Proxy Aliases
Section titled “Proxy Aliases”A container’s default service URLs embed its project and container IDs, so they work but nobody can remember or type them. A proxy alias gives the same service a second hostname that you choose: my-api.node-us.containers.hoody.com instead of the generated form.
This page covers how aliases are created, what they route to, how they handle paths and expiration, and how they interact with proxy permissions. It follows on from how the Hoody Proxy works.
API endpoints summary
Section titled “API endpoints summary”This page explains alias concepts and usage patterns. The endpoint reference carries the full request and response schemas.
Alias management:
- POST /api/v1/proxy/aliases - Create new alias
- GET /api/v1/proxy/aliases - List all aliases (with filters)
- GET /api/v1/proxy/aliases/{id} - Get alias details
- PATCH /api/v1/proxy/aliases/{id} - Update alias configuration
- PATCH /api/v1/proxy/aliases/{id}/state - Enable/disable alias
- DELETE /api/v1/proxy/aliases/{id} - Delete alias
Related:
- Proxy Permissions - Control who can access aliases
- Container Operations - Container lifecycle
Generated URLs and aliases
Section titled “Generated URLs and aliases”Default container URLs
Section titled “Default container URLs”Spawning a container produces service URLs automatically:
https://67e89abc123def456789abcd-890abcdef12345678901cdef-exec-1.node-us.containers.hoody.comThose URLs have a fixed set of properties:
- Created automatically, with nothing to configure
- Unique per service, built from cryptographic IDs
- Effectively unguessable, so sharing the URL is what grants access
- Working for every program the container runs
- Impossible to type or remember
- Unsafe to leak: if one is shared accidentally before permissions are configured, anyone holding the URL can reach the service
- Not brandable, since you cannot put one on a business card
Alias URLs
Section titled “Alias URLs”Create an alias and the service gets a hostname you choose:
POST /api/v1/proxy/aliases{ "container_id": "890abcdef12345678901cdef", "alias": "my-api", "program": "http", "port": 3000}Result:
https://my-api.node-us.containers.hoody.comBoth hostnames reach the same container and the same service.
Aliases in production
Section titled “Aliases in production”A container running a web server or an API can be reached two ways.
The cryptographic URL
Section titled “The cryptographic URL”https://67e89abc123def456789abcd-890abcdef12345678901cdef-http-8080.node-us.containers.hoody.comProblems in production:
- Exposes project and container IDs (48 characters of sensitive data)
- Impossible to remember or type
- Unprofessional for customers and users
- Cannot go on business cards, marketing material, or documentation
The alias
Section titled “The alias”https://my-api.node-us.containers.hoody.comBenefits:
- Keeps internal IDs out of the URL you hand out — though some programs still return them in their responses
- Short enough to remember and type
- Suited to public APIs and web services
- Usable as a CNAME target for a custom domain
The name is my-api, not api. A bare api — and anything starting api- — is one of the router-reserved names in Naming rules below: the alias is created, and the hostname never resolves.
Then connect your domain:
api.mycompany.com CNAME my-api.node-us.containers.hoody.comhttps://api.mycompany.com then routes to your container, and no ID appears in the URL.
The http program
Section titled “The http program”For a web server or API inside a container, use program: "http" with port:
POST /api/v1/proxy/aliases{ "container_id": "890abcdef12345678901cdef", "alias": "my-api", "program": "http", // Routes to container's HTTP service "port": 3000 // Port your server listens on inside the container}That maps to the web server running on the port you named (3000, 8080, 5000, or whatever your process listens on). The proxy then routes https://my-api.node-us.containers.hoody.com to that HTTP service automatically.
Typical production workflow:
- Deploy your Node.js, Python, or Go API in a container
- Create an alias with
program: "http"and your server’sport - Point your domain at the alias
- Configure authentication with proxy permissions
How aliases work
Section titled “How aliases work”Alias structure
Section titled “Alias structure”Aliases follow this pattern:
https://{alias}.{serverName}.containers.hoody.com └──┬──┘ └────┬────┘ Your Your Server Choice (where container runs)Alias names are unique per physical server, across every tenant hosted on it — and additionally unique within your own account across all servers. The {serverName} component reflects where the container runs, so a name taken on one server can still be created on a different server by a different account; your own account can hold a given name only once, wherever it lives.
Example:
- Container on
node-us→ Alias becomesmy-app.node-us.containers.hoody.com - The name
my-appis now taken onnode-us(for every tenant on that server) and cannot be claimed again there
Alias targets
Section titled “Alias targets”An alias points at one program inside a container:
POST /api/v1/proxy/aliases{ "container_id": "890abcdef12345678901cdef", "alias": "my-api", "program": "http", // Which program (use "http"/"https" for web servers) "port": 3000, // Port your server listens on inside the container "target_path": "/api/v1", // Optional: landing path for a root request "allow_path_override": true}Common programs: http, https, exec, ssh, terminal, display, code
http/https- HTTP/HTTPS servers (use withportto route to a server running inside the container)exec- Exec scripts as APIsssh- SSH accessterminal- Terminal interfacedisplay- Desktop environmentcode- Code editor interface
The program value must be a built-in Hoody program (such as those above) or a transport protocol (http, https, ssh); any other value is rejected.
A container can carry several aliases, pointing at different programs or at the same program with different configurations.
Alias creation
Section titled “Alias creation”A basic alias
Section titled “A basic alias”# Create a basic proxy alias for your containerhoody proxy aliases create --container-id $CONTAINER_ID --alias my-app --program http --port 3000const alias = await client.api.proxy.aliases.create({ container_id: CONTAINER_ID, alias: 'my-app', program: 'http', port: 3000});console.log(alias.data.url);// https://my-app.node-us.containers.hoody.comcurl -X POST "https://api.hoody.com/api/v1/proxy/aliases" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "container_id": "'$CONTAINER_ID'", "alias": "my-app", "program": "http", "port": 3000 }'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Creates a proxy alias for the container’s HTTP service, giving it a memorable hostname in place of the generated one.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases&method=POST&bearer_token=TOKEN&json={"container_id":"CONTAINER_ID","alias":"my-app","program":"http","port":3000}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
The service is now reachable at:
https://my-app.node-us.containers.hoody.comAuto-generated names
Section titled “Auto-generated names”Omit the alias parameter and the API generates a name for you:
The response carries a 48-character hexadecimal alias, auto-generated and unique, for example a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3.
Naming rules
Section titled “Naming rules”Valid alias names:
- 3-61 characters
- Lowercase letters (a-z)
- Numbers (0-9)
- Hyphens (-)
- Must start with letter or number
- Must end with letter or number
- Pattern:
^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - Reserved:
containers(exact label), plus four service names,egress,workspaces,workspaceandos, each rejected as the whole label or followed by a hyphen. Soos,os-adminandworkspace-2are all refused, while a distinct label that merely shares the opening letters (egressmyapp,ostrich) is fine: the match is on a label boundary, not on any string that starts the same way. - Also rejected: names that would impersonate a container URL — anything starting with the
{projectId}-{containerId}form (two 24-hex-char labels), or that shape with anegress-/workspaces-/proxy-service prefix.
Valid examples:
my-apistaging-frontendapp-v2prod
Invalid examples:
-myapp(starts with hyphen)my_api(underscore not allowed)MY-APP(uppercase not allowed)
Four more names the router refuses
Section titled “Four more names the router refuses”api, admin, system and internal are reserved by the reverse proxy on the same label-boundary rule: the bare word and anything starting api-, admin-, system- or internal- is dropped before the router looks the alias up. apiary and administer are distinct labels and route normally.
The create call does not enforce this. POST /api/v1/proxy/aliases with "alias": "api-v1" returns 201 Created with a url field, and that URL then resolves to nothing — no error, no warning, nothing in the response to tell you the hostname is dead. Treat those four words the way you treat egress: never as the whole name, never as the first segment.
Path routing
Section titled “Path routing”Target path
Section titled “Target path”Route requests to a specific base path in your container:
Routing behavior:
Incoming Request:https://my-api.node-us.containers.hoody.com/
Routed To Container:/api/v1
(a root request uses target_path; with allow_path_override true, other request paths are used as sent, so /users arrives as /users)Incoming Request:https://my-api.node-us.containers.hoody.com/users
Routed To Container:/users
(pass-through, no modification)Path override
Section titled “Path override”allow_path_override decides which paths a visitor can reach through the alias. It applies the same way to the alias hostname and to a custom domain pointed at it.
true (the default) forwards every path as sent. A root request (/) lands on target_path, with the query written in target_path followed by the visitor’s own parameters. A request that carries its own path is forwarded from the container root; target_path is never a prefix.
# allow_path_override: truehttps://my-api.node-us.containers.hoody.com/ → /api/v1 (target_path)https://my-api.node-us.containers.hoody.com/api/v1/users → /api/v1/users (forwarded as sent)https://my-api.node-us.containers.hoody.com/admin → /admin (forwarded as sent)false makes the alias serve only the root, or target_path itself. The proxy permissions are checked first; then:
- a request to
/, or totarget_pathitself, lands ontarget_path; one trailing slash is accepted, so/api/v1/statusand/api/v1/status/are both served; - any other path, sub-paths and assets included, is refused with
404, the error codeALIAS_PATH_PINNEDand the message “This alias serves only its root and its target path”; - a query parameter written in
target_pathkeeps its value: a visitor’s parameter with the same name is dropped; - the instance selectors the proxy sets itself (such as
terminal_id,displayorid) stay as the proxy set them; - the visitor’s method, request body, other query parameters, WebSocket upgrade and
Rangeheader are passed on, so a pinned alias can front one API endpoint that takes aPOSTwith a JSON body.
# allow_path_override: false, target_path "/api/v1/status?format=json"https://status.node-us.containers.hoody.com/ → /api/v1/status?format=jsonhttps://status.node-us.containers.hoody.com/?format=xml → /api/v1/status?format=json (target_path's value wins)https://status.node-us.containers.hoody.com/?verbose=1 → /api/v1/status?format=json&verbose=1https://status.node-us.containers.hoody.com/api/v1/status → /api/v1/status?format=json (target_path itself)https://status.node-us.containers.hoody.com/api/v1/status/ → /api/v1/status?format=json (one trailing slash)https://status.node-us.containers.hoody.com/api/v1/status/x → 404 ALIAS_PATH_PINNED (sub-path)https://status.node-us.containers.hoody.com/users → 404 ALIAS_PATH_PINNEDUse false for a link to one fixed page or one fixed API endpoint. An interactive kit (terminal, code, browser, files), an app that loads further assets (CSS, JS, images) and an API with several routes need true: under false every request other than / and target_path answers 404.
The flag limits where the link goes, not who may use it: anyone who has the link can open it. Restrict who may with proxy permissions. Permissions authenticate per program and port and carry no request-path selector, so per-route rules such as /admin/* need a proxy hook or authorization inside your application.
Example: land the bare alias root on your API base path (/api/v1) instead of /, and keep every other route of the API reachable:
POST /api/v1/proxy/aliases{ "container_id": "890abcdef12345678901cdef", "alias": "public-api", "program": "http", "port": 3000, "target_path": "/api/v1", "allow_path_override": true}Host header
Section titled “Host header”A web app on an http service (http-<port>, or plain http/https) reached through an alias receives the host the visitor typed in Host: the alias hostname, or a custom domain pointed at it. An app that checks Host against an allow-list must list the alias address and every custom domain pointed at it, for example allowedHosts in the Vite or webpack dev server, ALLOWED_HOSTS in Django, an nginx server_name, or a Caddy site address; otherwise it refuses the request. Hoody’s own kits receive their container URL in Host; X-Forwarded-Host always carries the host the visitor typed. See Which Host header does my app receive?.
Multi-service aliases
Section titled “Multi-service aliases”One container can carry several aliases, one per service:
Result:
https://my-api.node-us.containers.hoody.com → HTTP servicehttps://my-scripts.node-us.containers.hoody.com → Exec scriptshttps://my-terminal.node-us.containers.hoody.com → TerminalEach hostname reaches a different program in the same container.
Alias lifecycle
Section titled “Alias lifecycle”The alias list
Section titled “The alias list”# List all aliaseshoody proxy aliases list
# Filter by projecthoody proxy aliases list --project-id $PROJECT_ID
# Filter by containerhoody proxy aliases list --container-id $CONTAINER_ID
# Find expired aliaseshoody proxy aliases list --expired true// List all aliasesconst all = await client.api.proxy.aliases.list();
// Filter by projectconst byProject = await client.api.proxy.aliases.list({ project_id: PROJECT_ID });
// Filter by containerconst byContainer = await client.api.proxy.aliases.list({ container_id: CONTAINER_ID });
// Find expired aliasesconst expired = await client.api.proxy.aliases.list({ expired: true });# List all aliasescurl "https://api.hoody.com/api/v1/proxy/aliases" \ -H "Authorization: Bearer $TOKEN"
# Filter by projectcurl "https://api.hoody.com/api/v1/proxy/aliases?project_id=$PROJECT_ID" \ -H "Authorization: Bearer $TOKEN"
# Filter by containercurl "https://api.hoody.com/api/v1/proxy/aliases?container_id=$CONTAINER_ID" \ -H "Authorization: Bearer $TOKEN"One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Lists your aliases, optionally filtered to one project or one container.
# List all
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases&method=GET&bearer_token=TOKEN&response=transparent
# By project
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases?project_id=PROJECT_ID&method=GET&bearer_token=TOKEN&response=transparent
# By container
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases?container_id=CONTAINER_ID&method=GET&bearer_token=TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Configuration updates
Section titled “Configuration updates”# Change alias target servicehoody proxy aliases update $ALIAS_ID --program exec --index 2 --target-path /v2
# Update expirationhoody proxy aliases update $ALIAS_ID --expires-at "2026-12-31T23:59:59Z"// Change alias target serviceawait client.api.proxy.aliases.update(ALIAS_ID, { program: 'exec', index: 2, target_path: '/v2'});
// Update expirationawait client.api.proxy.aliases.update(ALIAS_ID, { expires_at: '2026-12-31T23:59:59Z'});# Change alias target servicecurl -X PATCH "https://api.hoody.com/api/v1/proxy/aliases/$ALIAS_ID" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"program": "exec", "index": 2, "target_path": "/v2"}'
# Update expirationcurl -X PATCH "https://api.hoody.com/api/v1/proxy/aliases/$ALIAS_ID" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"expires_at": "2026-12-31T23:59:59Z"}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Updates an existing alias’s target program or its expiration timestamp.
# Change target
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases/ALIAS_ID&method=PATCH&bearer_token=TOKEN&json={"program":"exec","index":2,"target_path":"/v2"}&response=transparent
# Update expiration
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases/ALIAS_ID&method=PATCH&bearer_token=TOKEN&json={"expires_at":"2026-12-31T23:59:59Z"}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Enabled state
Section titled “Enabled state”Disable an alias without deleting its configuration. Disable, enable, update and delete are not instant: they usually reach the alias URL within about 30 seconds and can take longer, and until then the alias keeps its previous behavior.
# Re-enable aliashoody proxy aliases enable $ALIAS_ID
# Disable aliashoody proxy aliases disable $ALIAS_ID// Disable aliasawait client.api.proxy.aliases.disable(ALIAS_ID, {});
// Re-enable aliasawait client.api.proxy.aliases.enable(ALIAS_ID, {});# Disable aliascurl -X PATCH "https://api.hoody.com/api/v1/proxy/aliases/$ALIAS_ID/state" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": false}'
# Re-enable aliascurl -X PATCH "https://api.hoody.com/api/v1/proxy/aliases/$ALIAS_ID/state" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": true}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Toggles an alias’s routing on or off without deleting its configuration.
# Disable
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases/ALIAS_ID/state&method=PATCH&bearer_token=TOKEN&json={"enabled":false}&response=transparent
# Re-enable
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases/ALIAS_ID/state&method=PATCH&bearer_token=TOKEN&json={"enabled":true}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
This takes an API offline temporarily for maintenance, without losing the alias configuration.
Deletion
Section titled “Deletion”# Permanently remove aliashoody proxy aliases delete $ALIAS_IDawait client.api.proxy.aliases.delete(ALIAS_ID);curl -X DELETE "https://api.hoody.com/api/v1/proxy/aliases/$ALIAS_ID" \ -H "Authorization: Bearer $TOKEN"One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Permanently removes the alias; the alias URL usually stops serving within about 30 seconds, and until then a recreate of the same name can return 409 (retry after a short wait).
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/proxy/aliases/ALIAS_ID&method=DELETE&bearer_token=TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
The alias name frees up once the deletion reaches the alias URL, usually within about 30 seconds; until then a recreate of the same name can return 409 Conflict, so retry after a short wait. Deletion itself is refused with 409 Conflict while the alias’s container is finalizing a claim — retry once the claim completes.
After you delete an alias, the same name can be claimed by another account. Do not rely on long-lived cookies or credentials on an alias address.
Expiration
Section titled “Expiration”An alias can expire on its own:
After expiration:
- Alias stops routing traffic automatically
- Returns 404 for all requests
- Configuration is preserved; clear
expires_atand setenabled: trueto restore routing.
Expiration formats:
On create (POST), expires_at must be an ISO 8601 string, or null for no expiration. On update (PATCH), the route schema also accepts a numeric Unix timestamp in seconds or milliseconds alongside the ISO string and null. Prefer ISO 8601 everywhere.
{ "expires_at": "2027-07-12T00:00:00.000Z" }{ "expires_at": "2026-12-31T23:59:59.000Z" }{ "expires_at": 1783987200 }{ "expires_at": null }Use cases:
- Demo environments - Auto-expire after customer trial
- Temporary access - Event-specific URLs
- Staged rollouts - Beta URLs that expire when moving to prod
Common patterns
Section titled “Common patterns”Production API alias
Section titled “Production API alias”A permanent alias for an API service:
Access:
https://prod-api.node-us.containers.hoody.com/→ Routes to container's /api/v1
https://prod-api.node-us.containers.hoody.com/users→ Forwarded as /users (allow_path_override true forwards other paths as sent)One container, several aliases
Section titled “One container, several aliases”Three aliases on one container, each pointing at a different program:
Result:
https://app.node-us.containers.hoody.com → Web servicehttps://app-terminal.node-us.containers.hoody.com → Terminalhttps://app-scripts.node-us.containers.hoody.com → Exec scriptsVersion aliases
Section titled “Version aliases”Aliases can carry API versions:
Clients can choose:
https://v1-api.node-us.containers.hoody.com → Old versionhttps://v2-api.node-us.containers.hoody.com → New versionhttps://beta-api.node-us.containers.hoody.com → Beta (same as v2)When ready: Delete v1-api, rename v2-api → v1-api, or update client references.
Staging to production promotion
Section titled “Staging to production promotion”A typical deployment workflow:
# 1. Develop in container with cryptographic URLhttps://67e89abc123def456789abcd-890abcdef12345678901cdef-exec-1.node-us.containers.hoody.com
# 2. Create staging alias when ready for testingPOST /api/v1/proxy/aliases{ "alias": "staging-app", "container_id": "890abcdef12345678901cdef", "program": "http", "port": 3000 }# → https://staging-app.node-us.containers.hoody.com
# 3. Test with team, clients, QA
# 4. Snapshot tested containerPOST /api/v1/containers/890abcdef12345678901cdef/snapshots{ "alias": "pre-prod-2025-11-09" }
# 5. Create production aliasPOST /api/v1/proxy/aliases{ "alias": "prod-app", "container_id": "890abcdef12345678901cdef", "program": "http", "port": 3000 }# → https://prod-app.node-us.containers.hoody.com
# 6. If issues, instant rollback via snapshot# Use data.snapshot.name returned by POSTPUT /api/v1/containers/890abcdef12345678901cdef/snapshots/$SNAPSHOT_NAMEOperations across many aliases
Section titled “Operations across many aliases”Filters and lookups
Section titled “Filters and lookups” Bulk updates
Section titled “Bulk updates”Update many aliases programmatically:
// Example: Update all staging aliases to a new landing pathconst stagingAliases = await fetch( 'https://api.hoody.com/api/v1/proxy/aliases?project_id=67e89abc123def456789abcd', { headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` } }).then(r => r.json());
for (const alias of stagingAliases.data.aliases) { await fetch(`https://api.hoody.com/api/v1/proxy/aliases/${alias.id}`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ target_path: '/v2' }) });}Advanced routing
Section titled “Advanced routing”Script routing for hoody-exec
Section titled “Script routing for hoody-exec”An alias for program: "exec" hands the whole script tree a short hostname. The path you request is the route:
Scripts in the container:
api/users.tsapi/posts.tswebhooks/stripe.tsAccess by path:
https://my-scripts.node-us.containers.hoody.com/api/users→ Executes api/users.ts
https://my-scripts.node-us.containers.hoody.com/webhooks/stripe→ Executes webhooks/stripe.tsThe alias replaces the hostname; everything after it is the route, exactly as on the generated -exec-1 URL. Combine it with target_path if you want the alias to land somewhere other than the script root.
See: Hoody Exec → for complete script routing documentation.
Multiple instances
Section titled “Multiple instances”Target different instances of the same program:
Result:
https://frontend.node-us.containers.hoody.com → HTTP service on port 3000https://backend.node-us.containers.hoody.com → HTTP service on port 8080Aliases as CNAME targets
Section titled “Aliases as CNAME targets”An alias is the CNAME target for a custom domain.
Step 1: Create alias
Step 2: Point your domain to the alias
# DNS configuration at your domain providerapi.mycompany.com CNAME myapp-prod.node-us.containers.hoody.comStep 3: Automatic SSL
Hoody provisions a Let’s Encrypt certificate for api.mycompany.com automatically. The custom domain serves HTTPS within minutes.
Result:
https://api.mycompany.com → CNAME →https://myapp-prod.node-us.containers.hoody.com → Routes to →Container's HTTP serviceSee: Connect a Domain → for complete custom domain setup.
Security considerations
Section titled “Security considerations”Alias uniqueness
Section titled “Alias uniqueness”Aliases must be unique per physical server, across every tenant hosted on it — and unique within your own account across all servers.
If anyone, including another tenant, has claimed my-app on the same physical server, you cannot use it there. The API returns a 409 Conflict response (Alias is already in use.). On a different server the name is available again to other accounts, but not to yours if you still hold it elsewhere.
Solution: Choose descriptive, unique aliases:
- Add your company name:
acme-api - Add identifier:
my-app-prod - Use generated names when uniqueness is uncertain
Cryptographic URLs vs aliases
Section titled “Cryptographic URLs vs aliases”Cryptographic URLs (Default)
https://67e89abc123def456789abcd-890abcdef12345678901cdef-exec-1.node-us.containers.hoody.comSecurity:
- Unguessable (2^96 combinations)
- Share URL = grant access
- Don’t share = private
- Suited to development and collaboration
Usability:
- Impossible to remember
- Can’t type manually
- Not brandable
Aliases (Production)
https://my-api.node-us. containers.hoody.comSecurity:
- Guessable (if known pattern)
- Discoverable (enumeration possible)
- IP whitelist recommended
- Add authentication via permissions
Usability:
- Memorable
- Typeable
- Brandable
- Professional
Best practice:
- Development: Use cryptographic URLs (secure by obscurity)
- Production: Use aliases + permissions (secure by authentication)
Permissions on aliases
Section titled “Permissions on aliases”Aliases work with proxy permissions:
Then configure permissions on a separate endpoint:
PUT /api/v1/containers/{id}/proxy/permissions{ "project": "67e89abc123def456789abcd", "container": "890abcdef12345678901cdef", "groups": { "authenticated": { "type": "jwt", "secret": "a-long-random-signing-key-with-32-plus-chars", "algorithm": "HS256", "sources": ["header:Authorization"] } }, "permissions": { "authenticated": { "http": [3000] } }, "default": "deny"}Both URLs now require authentication:
https://67e89abc123def456789abcd-890abcdef12345678901cdef-http-3000.node-us.containers.hoody.com → Requires JWThttps://public-api.node-us.containers.hoody.com → Requires JWTThe alias and the cryptographic URL apply the same permissions.
Worked example
Section titled “Worked example”A complete workflow from development to production.
1. Develop in container (use the cryptographic URL)
https://67e89abc123def456789abcd-890abcdef12345678901cdef-exec-1.node-us.containers.hoody.com2. Create staging alias for team testing
Result: https://staging-myapp.node-us.containers.hoody.com
3. Configure staging with IP whitelist (office only)
# Fetch the current permissions document and save its etag# (the server returns 428 if If-Match is omitted, 412 if it is stale)CURRENT_ETAG=$(curl -s "https://api.hoody.com/api/v1/containers/890abcdef12345678901cdef/proxy/permissions" \ -H "Authorization: Bearer $HOODY_TOKEN" | jq -r '.data.etag')
curl -X PUT "https://api.hoody.com/api/v1/containers/890abcdef12345678901cdef/proxy/permissions" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "If-Match: $CURRENT_ETAG" \ -H "Content-Type: application/json" \ -d '{ "project": "67e89abc123def456789abcd", "container": "890abcdef12345678901cdef", "groups": { "office": { "type": "ip", "range": "203.0.113.0/24" } }, "permissions": { "office": { "http": [3000] } }, "default": "deny" }'4. Team tests on staging-myapp.node-us.containers.hoody.com
5. Snapshot when ready
6. Create production alias
Result: https://myapp.node-us.containers.hoody.com
7. Point custom domain
DNS: api.mycompany.com CNAME myapp.node-us.containers.hoody.comResult: https://api.mycompany.com (automatic SSL)
8. Configure production permissions (JWT auth)
# Fetch the current permissions document and save its etag# (the server returns 428 if If-Match is omitted, 412 if it is stale)CURRENT_ETAG=$(curl -s "https://api.hoody.com/api/v1/containers/890abcdef12345678901cdef/proxy/permissions" \ -H "Authorization: Bearer $HOODY_TOKEN" | jq -r '.data.etag')
curl -X PUT "https://api.hoody.com/api/v1/containers/890abcdef12345678901cdef/proxy/permissions" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "If-Match: $CURRENT_ETAG" \ -H "Content-Type: application/json" \ -d '{ "project": "67e89abc123def456789abcd", "container": "890abcdef12345678901cdef", "groups": { "customers": { "type": "jwt", "secret": "production-jwt-secret", "sources": ["header:Authorization"] } }, "permissions": { "customers": { "http": [3000] } }, "default": "deny" }'9. Production is live
Development: https://67e89abc123def456789abcd-890abcdef12345678901cdef-exec-1.node-us.containers.hoody.com (crypto URL)Staging: https://staging-myapp.node-us.containers.hoody.com (IP-restricted)Production: https://api.mycompany.com (JWT auth, custom domain)After step 8, the full-replacement PUT removes the office group and installs the customers JWT group. Both HTTP aliases require JWT, and the exec URL is denied by the default policy. Use separate containers or different program/port rules when staging and production need different policies.
Useful questions
Section titled “Useful questions”Can I use the same alias name on different servers?
Section titled “Can I use the same alias name on different servers?”Not with the same account. Alias names are unique per physical server (across all tenants on it) and unique within your account across all servers — so a different account can reuse a name on a different server, but your own second claim of the same name returns 409 Conflict wherever it is. On the same server a name taken by anyone fails the same way; pick a variant like my-app-eu.
What happens if I delete a container that has aliases?
Section titled “What happens if I delete a container that has aliases?”Deleting a container also deletes its aliases; their URLs stop resolving shortly after. You do not need to delete aliases first. The names become free again, but an alias cannot be moved to another container: the target is fixed at create time, so reuse means creating a new alias with the same name against the new container.
Can several aliases point to one service?
Section titled “Can several aliases point to one service?”Yes. Create several aliases with different names, all pointing to the same container_id and program. This is useful for versioning (v1-api and v2-api both pointing to the same container initially) or for multi-brand domains.
Do aliases work with proxy permissions?
Section titled “Do aliases work with proxy permissions?”Yes. When you configure proxy permissions for a container, they apply to both the cryptographic URL and every alias pointing to that container. One permission configuration covers all entry points.
Can I create an alias before the container is running?
Section titled “Can I create an alias before the container is running?”Yes. You can create aliases for stopped containers. The alias exists, but requests will fail until you start the container. Useful for pre-configuring production URLs before deployment.
How do target_path and allow_path_override differ?
Section titled “How do target_path and allow_path_override differ?”target_path supplies the destination for a root request (/), including any query written in it; it is never used as a prefix for other paths. allow_path_override decides what happens to every other path: true (the default) forwards it as sent, false refuses it with 404 ALIAS_PATH_PINNED (apart from target_path itself, which lands on target_path like /) and keeps the parameters written in target_path from being overridden. See Path override. Neither field controls who can open the alias: anyone with the link can. To control who can reach the container, use proxy permissions.
How do I prevent someone from guessing my alias names?
Section titled “How do I prevent someone from guessing my alias names?”Use long, specific aliases (acme-prod-api-v2-us-2025) instead of generic ones (api, app). Better still, combine the alias with proxy permissions for authentication, so that guessing the name does not grant access.
Can I have an alias without specifying program or index?
Section titled “Can I have an alias without specifying program or index?”The program is required. For built-in programs, index is optional and defaults to 1, so {"container_id": "…", "program": "terminal"} is a valid alias. For http/https, always supply port (which index is also read as) with the backend’s listening port; omitting it routes to port 1, not port 80 or 443. The program is mandatory because one container runs several services and the alias has to name the one it routes to.
Do aliases survive snapshot and restore?
Section titled “Do aliases survive snapshot and restore?”Aliases are stored separately, not in the container. If you snapshot container A with alias my-app, then restore to container B, the alias still points to container A. To route to container B, delete the alias and create a new one targeting container B’s ID (the target container cannot be changed on an existing alias).
Can I see which custom domains point to my aliases?
Section titled “Can I see which custom domains point to my aliases?”The GET /api/v1/proxy/aliases/{id} endpoint shows alias configuration, but not which custom domains CNAME to it (that’s in your DNS provider). Best practice: document your CNAME mappings externally (spreadsheet, wiki, infrastructure-as-code).
Troubleshooting
Section titled “Troubleshooting”Alias already exists
Section titled “Alias already exists”Error:
{ "statusCode": 409, "error": "Conflict", "message": "Alias is already in use.", "data": { "field": "alias", "value": "my-app", "scope": "server" }}A scope of "server" means another account already holds the name on this physical server; "account" means your own account already holds it — on any server, including this one, because the check against your own aliases runs first. For an "account" conflict, delete or rename your existing alias to reuse the name.
Solutions:
- Choose a different alias name
- Use a suffix:
my-app-v2,my-app-prod - Check existing aliases across your whole account — the conflict can be your own alias on another project or server:
GET /api/v1/proxy/aliases(unfiltered)
Alias not routing
Section titled “Alias not routing”Check:
- Enabled status:
GET /api/v1/proxy/aliases/{id}→ Checkenabled: true - Container running:
GET /api/v1/containers/{id}→ Checkstatus: "running" - Service running: Check container’s service is actually started
- Permissions: Verify you can access via cryptographic URL first
DNS propagation delay
Section titled “DNS propagation delay”For custom domains:
- CNAME changes take 5-60 minutes to propagate globally
- Test from multiple locations or wait before troubleshooting
- Use
dig api.mycompany.comto verify DNS points to alias
Coexistence with cryptographic URLs
Section titled “Coexistence with cryptographic URLs”When you create an alias, the original cryptographic URL still works:
Alias:https://my-api.node-us.containers.hoody.com
Original (still works):https://67e89abc123def456789abcd-890abcdef12345678901cdef-exec-1.node-us.containers.hoody.comBoth route to the same container service, under the same permissions.
Use case:
- Share aliases publicly (clean URLs)
- Keep cryptographic URLs for internal tools (unguessable security)
What’s next
Section titled “What’s next”- Connect a Domain → - Point your custom domain to an alias
- Set Permissions → - Add authentication to protect aliases
Summary of this page:
- Aliases create short URLs of the form
my-app.{serverName}.containers.hoody.com - They map to specific container programs (http, exec, terminal, and the rest)
- They support path routing and access control
- They serve as CNAME targets for custom domains
- They can be temporary (expiration) or permanent