Prespawn
Containers are created and warmed ahead of demand, so a create request is answered without waiting for provisioning.
Spawning a container, reaching it over its URL, and running whatever you want inside it is the base of the platform. This page covers what sits on top of that base: the features that come up once you are running more than one container, or running containers on someone else’s behalf.
Prespawn pools remove the wait for a container to provision. Realms scope what an API token can see, which is what multi-tenant isolation is built on. Snapshots record a container’s whole filesystem, so you can restore or branch it the way you would a Git repository. Storage shares let containers mount the same directory instead of each keeping a copy. Network routing changes a container’s exit IP with one API call.
Each section below gives the concept, a short example, and a link to the reference page.
Prespawn
Containers are created and warmed ahead of demand, so a create request is answered without waiting for provisioning.
Server pools
Members share one server’s capacity, with no per-user charge.
Realm isolation
Scope API visibility by realm. Tokens cannot see resources outside their assigned realm.
Network routing
Route container traffic through a SOCKS5 or HTTP proxy, or block outbound traffic entirely, with one API call.
Storage shares
Share directories between containers, readonly or readwrite, on the same server or across servers.
Copy & Sync
Clone a container to a different server, then sync the copy with the source incrementally.
Snapshots
Capture a container’s complete state, restore it to any point in time, or branch from it.
A container takes 1-5 seconds to provision. For an on-demand SaaS, a webhook handler, or an AI agent launcher, that cold start is too slow.
Prespawn removes it. You define a template covering the base image, resource limits, and Kit configuration, and Hoody keeps a pool of containers created and warmed against it. When you ask for a container, one is assigned from the pool, so nothing boots and nothing is provisioned inside that request.
# Create a container. If a warm pool exists for this server the container# is claimed from it, otherwise it is created on demand.hoody containers create \ --project $PROJECT_ID \ --server-id $SERVER_ID \ --name "instant" \ --hoody-kitimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Create a container. It is claimed from the warm pool if one exists.await client.api.containers.create(PROJECT_ID, { server_id: SERVER_ID, name: 'instant', hoody_kit: true});
// If a prespawn pool is warm for this server, the container is assigned instantly# Create a container (claimed from the warm pool if one exists)curl -X POST "https://api.hoody.com/api/v1/projects/$PROJECT_ID/containers" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "server_id": "'"$SERVER_ID"'", "name": "instant", "hoody_kit": 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
Runs through the curl-1 service of a container you already have. If a warm
pool exists for this server, the container returned is claimed from it instead
of provisioned fresh.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/projects/PROJECT_ID/containers&method=POST&bearer_token=TOKEN&json={"server_id":"SERVER_ID","name":"instant","hoody_kit":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.
A server pool shares one server’s bare metal capacity across team members without giving everyone root access to the host. Create a pool, invite members, and each member can spawn containers on that hardware.
# Create a server poolhoody pools create --name "engineering-team"
# Invite a team memberhoody pools members invite $POOL_ID --username "alice" --role "user"// Create a pool and invite team membersconst pool = await client.api.pools.create({ name: 'engineering-team' });
await client.api.poolMembers.invite(pool.data.id, { username: 'alice', role: 'user'});# Create a server poolcurl -X POST "https://api.hoody.com/api/v1/pools" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "engineering-team"}'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 the pool. Invite members afterward with the CLI or SDK tab; the HTTP tab above only documents pool creation.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/pools&method=POST&bearer_token=TOKEN&json={"name":"engineering-team"}&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.
Deep dive: Server Management | Servers API
A realm-restricted token cannot see resources outside its realm. They are not returned with an access-denied error; they are absent from the API that token talks to. Multi-tenant isolation, environment separation, and AI agent sandboxing are all built on that.
# Create a strict realm-only token for staging (refuses the base domain).# --no-allow-no-realm is the negated form of --allow-no-realm; it sets# allow_no_realm: false, matching the SDK tab.hoody auth create \ --alias "ci-staging" \ --realm-ids "507f1f77bcf86cd799439011" \ --no-allow-no-realm
# This token can only operate on the realm-scoped hosthoody --base-url "https://507f1f77bcf86cd799439011.api.hoody.com" \ containers list// Create realm-restricted tokenconst token = await client.api.authTokens.create({ alias: 'ci-staging', realm_ids: ['507f1f77bcf86cd799439011'], allow_no_realm: false});
// Use realm-scoped clientconst realmClient = new HoodyClient({ baseURL: 'https://507f1f77bcf86cd799439011.api.hoody.com', token: token.data.token});
// Only sees staging resourcesconst containers = await realmClient.api.containers.list();# List containers in a specific realmcurl "https://507f1f77bcf86cd799439011.api.hoody.com/api/v1/containers" \ -H "Authorization: Bearer $REALM_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 containers as seen by a realm-scoped token — only what that realm contains, not the account’s full container list.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://507f1f77bcf86cd799439011.api.hoody.com/api/v1/containers&method=GET&bearer_token=REALM_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.
Deep dive: Realms Concept | Realms Foundation | Realms API
Network configuration changes where a container’s traffic exits. All outbound TCP can be routed through a SOCKS5 proxy, routed through an HTTP proxy, or blocked entirely. Nothing inside the container is configured for it, and no process in the container has to be aware of the change.
# Route container traffic through a SOCKS5 proxyhoody network update --container $CONTAINER_ID \ --type socks5 \ --proxy "socks5://user:pass@proxy.example.com:1080"// Route through SOCKS5await client.api.containers.updateNetworkConfig(CONTAINER_ID, { type: 'socks5', proxy: 'socks5://user:pass@proxy.example.com:1080'});
// Or block all outbound trafficawait client.api.containers.updateNetworkConfig(CONTAINER_ID, { type: 'block' });# Route container through SOCKS5 proxycurl -X PATCH "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/network" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "type": "socks5", "proxy": "socks5://user:pass@proxy.example.com:1080" }'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
Routes the container’s outbound traffic through the given SOCKS5 proxy. Nothing inside the container needs to be reconfigured for it to take effect.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/network&method=PATCH&bearer_token=TOKEN&json={"type":"socks5","proxy":"socks5://user:pass@proxy.example.com:1080"}&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.
Deep dive: Network Configuration | Container Network API
A container can share one of its directories with other containers, including containers on other servers. The source container controls which directory is shared and the access mode; the target container controls whether to mount it. Use readonly to distribute configuration, and readwrite when containers need to write to the same files.
# Share a directory with another container (readonly)hoody storage create --container $SOURCE_CONTAINER_ID \ --source-path "/hoody/storage/shared-assets" \ --target-container-id $TARGET_CONTAINER_ID \ --mode readonly
# Share with an entire project (all containers)hoody storage create --container $SOURCE_CONTAINER_ID \ --source-path "/hoody/storage/config" \ --target-project-id $PROJECT_ID \ --mode readonly// Share directory with another containerawait client.api.storageShares.create(SOURCE_CONTAINER_ID, { source_path: '/hoody/storage/shared-assets', target_container_id: TARGET_CONTAINER_ID, mode: 'readonly', alias: 'assets'});
// The target mount path is assigned automatically by the server# Create a storage sharecurl -X POST "https://api.hoody.com/api/v1/containers/$SOURCE_CONTAINER_ID/storage/shares" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "source_path": "/hoody/storage/shared-assets", "target_container_id": "'"$TARGET_CONTAINER_ID"'", "mode": "readonly", "alias": "assets" }'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
Shares a directory from SOURCE_CONTAINER_ID with TARGET_CONTAINER_ID. The share mounts on TARGET_CONTAINER_ID automatically; the target can unmount it later if it does not want it.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/SOURCE_CONTAINER_ID/storage/shares&method=POST&bearer_token=TOKEN&json={"source_path":"/hoody/storage/shared-assets","target_container_id":"TARGET_CONTAINER_ID","mode":"readonly","alias":"assets"}&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.
Deep dive: Shared Storage | Cloud Storage | SQLite Drive | Ramdisk
Copy produces a full independent duplicate of a container, on a different server and in a different project if you want, holding the same state. It takes minutes. Sync then brings that copy up to date with the source and transfers only what changed, which takes seconds.
# Copy production container to EU server for geographic redundancyhoody containers copy $PROD_CONTAINER_ID \ --target-project-id $PROJECT_ID \ --target-server-id $EU_SERVER_ID \ --name "prod-eu-replica"
# Later: sync the copy with production changeshoody containers sync $EU_COPY_ID// Copy to different serverconst copy = await client.api.containers.copy(PROD_CONTAINER_ID, { target_project_id: PROJECT_ID, target_server_id: EU_SERVER_ID, name: 'prod-eu-replica'});
// Sync incrementally (only changes transferred)await client.api.containers.sync(copy.data.id);# Copy container to EU servercurl -X POST "https://api.hoody.com/api/v1/containers/$PROD_ID/copy" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "target_project_id": "'"$PROJECT_ID"'", "target_server_id": "'"$EU_SERVER_ID"'", "name": "prod-eu-replica" }'
# Sync copy with source changescurl -X POST "https://api.hoody.com/api/v1/containers/$EU_COPY_ID/sync" \ -H "Authorization: Bearer $HOODY_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
Copy duplicates the container on the target server; Sync, run later against the copy’s own id, transfers only what changed since.
# Copy
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/PROD_ID/copy&method=POST&bearer_token=TOKEN&json={"target_project_id":"PROJECT_ID","target_server_id":"EU_SERVER_ID","name":"prod-eu-replica"}&response=transparent
# Sync
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/EU_COPY_ID/sync&method=POST&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.
Deep dive: Copy & Sync | Copy & Sync API
A snapshot records a container’s complete filesystem state, and restoring one returns the container to that moment in seconds. Snapshots are copy-on-write, so creating one costs almost nothing. Take one before a risky change, a deployment, or an AI experiment.
# Snapshot before a risky changehoody snapshots create -c $CONTAINER_ID \ --alias "before-ai-refactor"
# Restore after a bad changehoody snapshots restore -c $CONTAINER_ID \ --name "snap-20260325-143045"
# List all snapshotshoody snapshots list -c $CONTAINER_ID// Create snapshot before deploymentconst snapshot = await client.api.containers.createSnapshot(CONTAINER_ID, { alias: 'pre-deploy-v2.1', expiry: 30 // auto-delete after 30 days});
// Restore after a failed deploymentawait client.api.containers.restoreSnapshot(CONTAINER_ID, snapshot.data.snapshot.name);# Create a snapshotcurl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "pre-deploy-v2.1", "expiry": 30}'
# Restore from snapshotcurl -X PUT "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots/snap-20260325-143045" \ -H "Authorization: Bearer $HOODY_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
Route these through a different running container’s curl-1
(OTHER_CONTAINER_ID below): restoring rebuilds CONTAINER_ID’s filesystem
and restarts it, so a request routed through CONTAINER_ID itself would
sever its own transport. Restoring replaces the container’s current state
with the snapshot’s, so take a fresh snapshot first if you want to keep
what’s there now.
# Create snapshot
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"pre-deploy-v2.1","expiry":30}&response=transparent
# Restore
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/snapshots/snap-20260325-143045&method=PUT&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.
Deep dive: Snapshots | Snapshots API