DeployHub automatically assigns each project a unique subdomain and supports custom domain configuration for professional deployments.
Automatic Subdomain Allocation
Every project receives a unique subdomain during deployment:
// From createDeployment.controller.js:158-179
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");
};
const allocation = await generateUniqueName(name);
Subdomain Format:
<project-name>-<random-6-chars>.deployhub.cloud
Example:
my-app-a8c7d2.deployhub.cloud
vite-react-x3k9m1.deployhub.cloud
api-server-p7q2w5.deployhub.cloud
The random suffix ensures uniqueness and prevents subdomain conflicts.
Subdomain Binding Model
Each subdomain is tracked in the Binding collection:
// From binding.model.js:3-21
const bindingSchema = new mongoose.Schema({
project: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Project'
},
subdomain: {
type: String,
required: true,
index: true // Fast lookups
},
port: {
type: String,
required: true,
},
customDomain: {
type: String
}
});
Key Fields:
subdomain - Unique identifier (indexed for fast routing)
port - Internal container port (80 for static, custom for Node.js)
project - Reference to Project document
customDomain - Optional custom domain
Redis Caching for Fast Routing
Subdomain mappings are cached in Redis for microsecond-latency lookups:
// From createDeployment.controller.js:194-199
await redisclient.hset(`subdomain:${allocation.subdomain}`, {
port: allocation.port,
projectId: newProject._id.toString(),
plan: newProject.plan
});
Redis Key Structure:
Key: subdomain:my-app-a8c7d2
Value: {
port: "80",
projectId: "507f1f77bcf86cd799439011",
plan: "free"
}
The reverse proxy checks Redis first before querying MongoDB, ensuring ultra-fast request routing.
Updating Subdomains
Users can change their subdomain to a custom value:
// From domain.controller.js:29-92
export const updateSubdomain = async (req, res) => {
const { subdomain } = req.body;
// Validation: lowercase letters, numbers, hyphens (3-40 chars)
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).',
});
}
// Check availability
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.',
});
}
const project = await Model.Project.findOne({
_id: req.params.id,
owner: req.user._id
});
const data = {
projectId: project._id,
oldcontainername: project.subdomain
};
project.subdomain = subdomain;
project.status = "building";
await project.save({ validateBeforeSave: false });
data.newcontainername = project.subdomain;
// Update binding
const allocation = await Model.Binding.findOne({project: project._id});
allocation.subdomain = subdomain;
await allocation.save({validateBeforeSave: false});
// Update Redis cache
await redisclient.del(`subdomain:${data.oldcontainername}`);
await redisclient.hset(`subdomain:${project.subdomain}`, {
port: allocation.port,
projectId: project._id.toString(),
plan: project.plan
});
// Recreate container with new name
await recreateContainer.add('deployhub-recreate-container', data);
res.status(200).json({
success: true,
subdomain: project.subdomain,
message: 'Subdomain updated. Project is restarting.',
});
};
API Endpoint:
PATCH /api/projects/:id/domains/subdomain
{
"subdomain": "my-custom-name"
}
Validation Rules:
- 3-40 characters
- Lowercase letters, numbers, hyphens only
- Must be unique across all projects
- No leading/trailing hyphens
Updating subdomain triggers a container recreation. Your app will be briefly unavailable during the switch.
Container Recreation Process
When subdomain changes, DeployHub recreates the container:
- Stop & Remove old container
- Update Redis cache
- Create new container with updated name
- Start container
// Worker handles container recreation
await recreateContainer.add('deployhub-recreate-container', {
projectId: project._id,
oldcontainername: 'old-name-a1b2c3',
newcontainername: 'new-name-x7y8z9'
});
Custom Domains
Project Model Schema
// From project.model.js:71-77
hascustomDomain: {
type: Boolean,
default: false
},
customDomain: {
type: String
},
subdomain: {
type: String
}
SSL Certificate Generation
DeployHub uses Certbot with Cloudflare DNS for SSL certificates:
// From generateSslCertificate.js:15-53
export async function generateCertificate(domain, email) {
// 1. Verify DNS points to server
const isValid = await isDomainPointingToServer(domain);
if (!isValid) {
throw new Error(`Domain ${domain} does not point to this server.`);
}
// 2. Run Certbot container
const container = await docker.createContainer({
Image: "certbot/dns-cloudflare",
Cmd: [
"certonly",
"--dns-cloudflare",
"--dns-cloudflare-credentials",
"/cf.ini",
"--dns-cloudflare-propagation-seconds",
"30",
"-d",
domain,
"-d",
`*.${domain}`, // Wildcard support
"--email",
email,
"--agree-tos",
"--no-eff-email"
],
HostConfig: {
Binds: [
"/home/rahul/docker/letsencrypt:/etc/letsencrypt",
"/home/rahul/secrets/cf.ini:/cf.ini:ro"
],
AutoRemove: true
}
});
await container.start();
await container.wait();
}
DNS Verification
// From generateSslCertificate.js:6-12
async function isDomainPointingToServer(domain) {
try {
const records = await dns.resolve4(domain);
return records.includes(SERVER_IP);
} catch {
return false;
}
}
Custom domains must have DNS A records pointing to DeployHub’s server IP before SSL generation.
Domain Management API
Get Project Domains
GET /api/projects/:id/domains
Response:
{
"success": true,
"project": {
"subdomain": "my-app-a8c7d2",
"hascustomDomain": true,
"customDomain": "myapp.example.com",
"plan": "pro"
}
}
Update Subdomain
PATCH /api/projects/:id/domains/subdomain
{
"subdomain": "my-new-name"
}
Response:
{
"success": true,
"subdomain": "my-new-name",
"message": "Subdomain updated. Project is restarting."
}
Networking Architecture
Docker Network
All containers run on the users Docker network:
// From deployworker.js:38-44
const container = await docker.createContainer({
Image: imageName,
name: `${bindingData.subdomain}`,
Env: envVariables,
HostConfig: {
NetworkMode: "users" // Isolated network
}
});
Container Naming
Containers are named using their subdomain:
Container Name = Subdomain
Examples:
my-app-a8c7d2
vite-react-x3k9m1
api-server-p7q2w5
Container names match subdomains exactly, making debugging and log inspection straightforward.
Reverse Proxy Routing
The Nginx reverse proxy routes requests based on subdomain:
- Request arrives at
my-app-a8c7d2.deployhub.cloud
- Redis lookup finds
{ port: "80", projectId: "...", plan: "free" }
- Proxy forwards to container
my-app-a8c7d2:80
- Response returned to client
Port Allocation
Static Sites: Port 80 (Nginx)
if (projectType === 'static') {
projectinternalPort = 80
}
Node.js Apps: Custom port
if (projectType === 'node') {
projectinternalPort = port // User-specified
}
Best Practices
Subdomain Naming
Choose descriptive names that reflect your project:
- ✅
portfolio-john, api-production, blog-staging
- ❌
test123, asdf, aaa
Custom Domains
For production deployments:
- Set up DNS A record pointing to server IP
- Wait for DNS propagation (up to 48 hours)
- Request SSL certificate
- Update project with custom domain
Avoid Frequent Changes
Each subdomain change recreates your container:
- Brief downtime during switch
- New container ID
- Stats reset
Limitations
- Maximum 20 subdomain generation attempts
- Subdomain length: 3-40 characters
- No uppercase letters or special characters (except hyphens)
- Custom domains require manual DNS configuration
- One custom domain per project
Troubleshooting
”This subdomain is already taken”
Choose a different name - subdomains must be unique across all DeployHub projects.
”Invalid subdomain”
Ensure your subdomain:
- Uses only lowercase letters, numbers, hyphens
- Is between 3-40 characters
- Doesn’t start or end with a hyphen
Custom domain not working
Check:
- DNS A record points to correct IP
- DNS has propagated (use
dig yourdomain.com)
- SSL certificate generated successfully
- Domain added to project settings