Overview
Every DeployHub project can be accessed via a subdomain or a custom domain. Subdomains are automatically generated during project creation and can be updated at any time.
Domain Structure
Projects support two types of domains:
Subdomain Automatically generated: {name}-{random}.deployhub.online
Custom Domain Your own domain: www.example.com
Get Project Domains
GET /api/projects/:id/domains Retrieve domain configuration for a project.
Authentication Required: Yes (JWT)
Path Parameters:
Project ID (MongoDB ObjectId)
Response:
{
"success" : true ,
"project" : {
"_id" : "507f1f77bcf86cd799439011" ,
"subdomain" : "my-website-a3f5k2" ,
"hascustomDomain" : false ,
"customDomain" : null ,
"plan" : "free"
}
}
With Custom Domain:
{
"success" : true ,
"project" : {
"_id" : "507f1f77bcf86cd799439011" ,
"subdomain" : "my-website-a3f5k2" ,
"hascustomDomain" : true ,
"customDomain" : "www.example.com" ,
"plan" : "pro"
}
}
Subdomain Generation
Subdomains are automatically generated during project creation using this algorithm:
Generation Logic
const generateUniqueName = async ( name ) => {
let attempts = 0 ;
while ( attempts < 20 ) {
const uniqueName = Math . random (). toString ( 36 ). substring ( 2 , 8 );
const subdomain = ` ${ name . toLowerCase () } - ${ uniqueName } ` ;
const existingDomain = await Model . Binding . findOne ({ subdomain });
if ( ! existingDomain ) {
const binding = new Model . Binding ({
project: projectId ,
subdomain: subdomain ,
port: projectinternalPort
});
await binding . save ({ validateBeforeSave: false });
return binding ;
}
attempts ++ ;
}
throw new Error ( "Could not generate unique subdomain after 20 attempts" );
};
Subdomain Format
Format: {project-name}-{6-char-random}
Project name is converted to lowercase
Random string is 6 characters (base-36: 0-9, a-z)
Up to 20 attempts to ensure uniqueness
Example: my-website-a3f5k2
Full URL
The complete domain is: {subdomain}.deployhub.online
Example: https://my-website-a3f5k2.deployhub.online
Update Subdomain
PATCH /api/projects/:id/domains/subdomain Change the subdomain for a project. This triggers a container rebuild.
Authentication Required: Yes (JWT)
Request Body:
New subdomain (without .deployhub.online) Validation: Must match pattern /^[a-z0-9-]{3,40}$/
Only lowercase letters, numbers, and hyphens
Length: 3-40 characters
Example Request:
{
"subdomain" : "my-awesome-site"
}
Response:
{
"success" : true ,
"subdomain" : "my-awesome-site" ,
"message" : "Subdomain updated. Project is restarting."
}
Subdomain Validation
if ( ! subdomain || ! / ^ [ a-z0-9- ] {3,40} $ / . test ( subdomain )) {
return res . status ( 400 ). json ({
success: false ,
message: 'Invalid subdomain. Use lowercase letters, numbers, hyphens (3-40 chars).' ,
});
}
Valid Examples:
my-site
production-app-2024
web-app-v2
my-project
Invalid Examples:
My-Site (uppercase not allowed)
my_site (underscores not allowed)
ab (too short, minimum 3 characters)
my.site (dots not allowed)
Uniqueness Check
const existing = await Model . Project . findOne ({
subdomain ,
_id: { $ne: req . params . id },
status: { $ne: 'deleted' },
}). select ( '_id' ). lean ();
if ( existing ) {
return res . status ( 409 ). json ({
success: false ,
message: 'This subdomain is already taken. Please choose another.' ,
});
}
The uniqueness check excludes the current project and deleted projects.
Subdomain Update Process
When updating a subdomain, several operations occur:
1. Update Project Status
project . subdomain = subdomain ;
project . status = "building" ;
await project . save ({ validateBeforeSave: false });
The project status is set to "building" during the subdomain transition.
2. Update Binding
const allocation = await Model . Binding . findOne ({ project: project . _id });
allocation . subdomain = subdomain ;
await allocation . save ({ validateBeforeSave: false });
The Binding model tracks the port allocation for the subdomain.
3. Update Redis Cache
// Remove old subdomain cache
await redisclient . del ( `subdomain: ${ data . oldcontainername } ` );
// Create new subdomain cache
await redisclient . hset ( `subdomain: ${ project . subdomain } ` , {
port: allocation . port ,
projectId: project . _id . toString (),
plan: project . plan
});
Redis is used for fast subdomain-to-project routing.
4. Queue Container Rebuild
const data = {
projectId: project . _id ,
oldcontainername: project . subdomain , // Before update
newcontainername: subdomain // After update
};
await recreateContainer . add ( 'deployhub-recreate-container' , data );
A worker recreates the Docker container with the new subdomain.
Binding Model
The Binding model manages domain-to-port mappings:
{
project : ObjectId , // Reference to Project
subdomain : String , // Subdomain (indexed)
port : String , // Internal port
customDomain : String // Optional custom domain
}
Index: subdomain field is indexed for fast lookups
Port Allocation
For static projects:
For Node.js projects:
projectinternalPort = port // User-specified port
Custom Domains
Custom domains are tracked in the project model:
hascustomDomain : {
type : Boolean ,
default : false
},
customDomain : {
type : String
}
Custom domain configuration endpoints are not shown in the provided source code. Custom domain setup may require additional DNS configuration and SSL certificate generation.
Redis Caching Strategy
Subdomains are cached in Redis for fast request routing:
Key Format: subdomain:{subdomain-name}
Cached Data:
{
port : "80" ,
projectId : "507f1f77bcf86cd799439011" ,
plan : "free"
}
This allows the reverse proxy to quickly determine which container to route requests to without querying the database.
Example: Update Subdomain
const updateSubdomain = async ( projectId , newSubdomain ) => {
const response = await fetch (
`/api/projects/ ${ projectId } /domains/subdomain` ,
{
method: 'PATCH' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ token } `
},
body: JSON . stringify ({
subdomain: newSubdomain
})
}
);
const data = await response . json ();
if ( data . success ) {
console . log ( `Subdomain updated to: ${ data . subdomain } ` );
console . log ( 'Container is being rebuilt...' );
// Poll for status
pollProjectStatus ( projectId );
} else {
console . error ( `Error: ${ data . message } ` );
}
};
const pollProjectStatus = async ( projectId ) => {
const response = await fetch ( `/api/projects/ ${ projectId } /overview` );
const { project } = await response . json ();
if ( project . status === 'building' ) {
console . log ( 'Still rebuilding...' );
setTimeout (() => pollProjectStatus ( projectId ), 5000 );
} else if ( project . status === 'live' ) {
console . log ( `Project is live at: ${ project . domain } ` );
}
};
Error Responses
{
"success" : false ,
"message" : "Invalid subdomain. Use lowercase letters, numbers, hyphens (3-40 chars)."
}
404 Not Found
{
"success" : false ,
"message" : "Project not found"
}
409 Conflict
{
"success" : false ,
"message" : "This subdomain is already taken. Please choose another."
}
500 Server Error
{
"success" : false ,
"message" : "Server error"
}
Security Considerations
Ownership Verification All domain operations verify that the authenticated user owns the project
Uniqueness Enforcement Subdomain uniqueness is enforced at the database level with indexed queries
Status Filtering Deleted projects are excluded from subdomain availability checks
Redis Sync Redis cache is updated atomically with database changes
Domain Display Logic
When displaying a project’s domain, use this logic:
const domain = project . hascustomDomain && project . customDomain
? project . customDomain
: ` ${ project . subdomain } .deployhub.online` ;
This ensures custom domains take precedence when configured.