Managing Containers
Section titled “Managing Containers”A container moves between states through HTTP endpoints: start, stop, force-stop, restart, pause, and resume. Every transition is recorded in a status log you can read back.
This page covers the lifecycle operations available once you have created a container.
API endpoints summary
Section titled “API endpoints summary”This page explains the concepts behind container operations. The endpoint reference carries the full request and response schemas.
Lifecycle operations:
- POST /api/v1/containers/{id}/start - Start stopped container
- POST /api/v1/containers/{id}/stop - Gracefully stop container
- POST /api/v1/containers/{id}/force-stop - Immediately terminate
- POST /api/v1/containers/{id}/restart - Stop then start
- POST /api/v1/containers/{id}/pause - Suspend container
- POST /api/v1/containers/{id}/resume - Resume suspended container
Status tracking:
- GET /api/v1/containers/{id}/status-logs - View state transition history
Related:
- GET /api/v1/containers/{id} - Get current container status
Container states
Section titled “Container states”The lifecycle operations move a container between these states:
startstopped ────────→ running ↑ ↓ │ │ pause │ ↓ │ paused │ ↓ │ │ resume │ ↓ └────── stop ── runningState descriptions
Section titled “State descriptions”| State | Description | Available Operations |
|---|---|---|
running | Container is active, all services available | stop, force-stop, restart, pause |
stopped | Container is halted, no processes running | start, restart, delete |
paused | Container suspended, state frozen in RAM | resume, stop, force-stop |
creating | Being provisioned (automatic) | (wait for completion) |
failed | Operation failed | delete |
copying | Being duplicated | (wait for completion) |
Start a container
Section titled “Start a container”Basic start
Section titled “Basic start” Response:
{ "statusCode": 200, "message": "Container operation completed successfully", "data": { "error": false, "operation": "start", "container_id": "890abcdef12345678901cdef", "project_id": "012cdef123456789abcdef01", "message": "Container started successfully", "status": "running" }}What happens:
- Container processes initialize
- Hoody Kit services start (if enabled)
- Network connectivity established
- Service URLs become accessible
Start usually completes in seconds. Heavier containers take longer.
When to start
Section titled “When to start”A container starts automatically when it is:
- Just created (if not
autostart: false) - On the server’s boot (if
autostart: true) - Restored from snapshot
Start it manually when:
- The container was previously stopped
- Maintenance or updates have finished
- You keep containers stopped when not in use to save cost
Stop a container
Section titled “Stop a container”Graceful stop
Section titled “Graceful stop” What happens:
- SIGTERM sent to all processes
- Processes given time to clean up
- If they do not exit, SIGKILL is sent (forced termination)
- Container status changes to
stopped
Use case: Normal shutdown before updates, maintenance, or resource conservation.
Force stop
Section titled “Force stop”Terminate every process immediately:
What happens:
- SIGKILL sent immediately (no graceful shutdown)
- All processes terminated instantly
- No cleanup time given
When to force-stop:
- Graceful stop hangs or times out
- Emergency situations (runaway process)
- Container is unresponsive
Restart a container
Section titled “Restart a container”Basic restart
Section titled “Basic restart” Equivalent to:
- Graceful stop
- Wait for stopped state
- Start
Use cases:
- Apply configuration changes
- Clear in-memory state
- Recover from issues
- Regular maintenance restarts
Restart time: one graceful stop plus one start, back to back.
Pause and resume
Section titled “Pause and resume” What happens:
- All container processes frozen
- State saved in RAM
- No CPU usage
- Minimal memory usage
- Service URLs return errors
Use cases:
- Temporary suspension during resource constraints
- Freeze state for debugging
- Quick pause/resume cycles
Resume
Section titled “Resume” What happens:
- Processes unfrozen
- Execution continues exactly where paused
- All state preserved (open files, network connections, terminal sessions)
- Service URLs become accessible again
Resume time: one to two seconds.
Pause vs stop
Section titled “Pause vs stop”Pause/Resume
Speed:
- Pause: ~1 second
- Resume: ~1-2 seconds
State:
- All processes frozen
- RAM state preserved
- Network connections maintained
- Open files preserved
Limitations:
- Requires RAM for state
- Already-running processes keep the environment they started with; they see updated variables only after they re-exec
- Snapshots cannot be created while paused; resume or stop first. Snapshots capture filesystem state only, not RAM
Best for: Quick suspension
Stop/Start
Speed:
- Stop: ~5-10 seconds
- Start: ~5-15 seconds
State:
- All processes terminated
- RAM state lost
- Network connections closed
- Filesystem preserved
Benefits:
- Configured services start from a clean runtime state; stop/start does not automatically make systemd services inherit the container’s environment variables
- Can create snapshots
- Zero RAM usage
- Clean state on restart
Best for: Maintenance, updates
Choose pause and resume for temporary suspension. Choose stop and start when you need a clean runtime state, or when configured services have to restart. Environment-variable changes generally do not require a restart: after a successful live sync, new exec and console processes see the updated values immediately, and dedicated containers env writes reach new SSH and console logins. Systemd services do not automatically inherit the container’s environment variables after a stop and start.
Operation patterns
Section titled “Operation patterns”Daily development workflow
Section titled “Daily development workflow”# Morning: Start your dev containerhoody containers manage $DEV_ID start
# Work all day via container URLs# https://{project}-{container}-terminal-1.{server}.containers.hoody.com# https://{project}-{container}-display-1.{server}.containers.hoody.com
# Evening: Pause (resumes in seconds tomorrow)hoody containers manage $DEV_ID pause// Morning: Start your dev containerawait client.api.containers.manage(DEV_ID, 'start');
// Work all day via container URLs...
// Evening: Pause (resumes in seconds tomorrow)await client.api.containers.manage(DEV_ID, 'pause');# Morning: Start your dev containercurl -X POST "https://api.hoody.com/api/v1/containers/{dev_id}/start" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Work all day via container URLs# https://{project}-{container}-terminal-1.{server}.containers.hoody.com# https://{project}-{container}-display-1.{server}.containers.hoody.com
# Evening: Pause (resumes in seconds tomorrow)curl -X POST "https://api.hoody.com/api/v1/containers/{dev_id}/pause" \ -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
Two links for the same daily routine: morning start, evening pause. Route them through a different running container’s curl-1 — DEV_ID is stopped when the morning link runs, and a stopped container can’t serve the request that starts it.
# Start
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/DEV_ID/start&method=POST&bearer_token=TOKEN&response=transparent
# Pause
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/DEV_ID/pause&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.
Why pause instead of stop:
- Resume takes one to two seconds, against a full stop and start cycle
- Terminal sessions are preserved
- Open editors stay open
- No in-memory state is lost
Maintenance window
Section titled “Maintenance window”# 1. Stop container gracefullyhoody containers manage $PROD_ID stop
# 2. Update configurationhoody containers update $PROD_ID --environment-vars CONFIG_VERSION=2.0
# 3. Restart with new confighoody containers manage $PROD_ID start// 1. Stop container gracefullyawait client.api.containers.manage(PROD_ID, 'stop');
// 2. Update configurationawait client.api.containers.update(PROD_ID, { environment_vars: { CONFIG_VERSION: '2.0' }});
// 3. Restart with new configawait client.api.containers.manage(PROD_ID, 'start');# 1. Stop container gracefullycurl -X POST "https://api.hoody.com/api/v1/containers/{prod_id}/stop" \ -H "Authorization: Bearer $HOODY_TOKEN"
# 2. Update configurationcurl -X PATCH "https://api.hoody.com/api/v1/containers/{prod_id}" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"environment_vars": {"CONFIG_VERSION": "2.0"}}'
# 3. Restart with new configcurl -X POST "https://api.hoody.com/api/v1/containers/{prod_id}/start" \ -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
Three links for the same maintenance sequence: stop, update the config, then start again. Route them through a different running container’s curl-1 — PROD_ID is stopped partway through and can’t serve the start call that brings it back.
# Stop
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/PROD_ID/stop&method=POST&bearer_token=TOKEN&response=transparent
# Update configuration
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/PROD_ID&method=PATCH&bearer_token=TOKEN&json={"environment_vars":{"CONFIG_VERSION":"2.0"}}&response=transparent
# Start
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/PROD_ID/start&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.
Resource optimization
Section titled “Resource optimization”Stop containers when not in use:
// Stop non-critical containers during off-hours.// `/api/v1/containers` returns all your containers — filter client-side by name.const nonCritical = ['staging-api', 'test-db', 'dev-frontend'];
const { data: all } = await fetch( 'https://api.hoody.com/api/v1/containers', { headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` }}).then(r => r.json());
const targets = all.containers.filter( c => nonCritical.includes(c.name) && c.status === 'running');
for (const c of targets) { await fetch( `https://api.hoody.com/api/v1/containers/${c.id}/stop`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` } } );}Automate with cron: Schedule stops at 6 PM, starts at 8 AM.
Emergency recovery
Section titled “Emergency recovery”# If the container is unresponsive, try a graceful stop firsthoody containers manage $CONTAINER_ID stop
# If that hangs or fails, force ithoody containers manage $CONTAINER_ID force-stop
# Restart cleanhoody containers manage $CONTAINER_ID start// Try graceful stop firstawait client.api.containers.manage(CONTAINER_ID, 'stop');
// If that hangs or fails, force itawait client.api.containers.manage(CONTAINER_ID, 'force-stop');
// Restart cleanawait client.api.containers.manage(CONTAINER_ID, 'start');# If the container is unresponsive, try a graceful stop firstcurl -X POST "https://api.hoody.com/api/v1/containers/{id}/stop" \ -H "Authorization: Bearer $HOODY_TOKEN"
# If that hangs or fails, force itcurl -X POST "https://api.hoody.com/api/v1/containers/{id}/force-stop" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Restart cleancurl -X POST "https://api.hoody.com/api/v1/containers/{id}/start" \ -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
Three links for the same recovery sequence: graceful stop, force-stop, then start. Route them through a different running container’s curl-1, since an unresponsive or stopped container can’t reliably serve requests aimed at itself.
# Graceful stop
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/stop&method=POST&bearer_token=TOKEN&response=transparent
# Force-stop
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/force-stop&method=POST&bearer_token=TOKEN&response=transparent
# Start
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/start&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.
Status logging
Section titled “Status logging”Get status logs
Section titled “Get status logs” Response:
{ "statusCode": 200, "message": "Status logs retrieved successfully", "data": { "logs": [ { "id": "63f8b0e5c9a1b2d3e4f5a6b7", "container_id": "890abcdef12345678901cdef", "from_status": "stopped", "to_status": "running", "transition_time": "2025-11-09T14:30:00.000Z", "duration_ms": 12450, "triggered_by": "user_abc123", "metadata": { "command": "start" } }, { "from_status": "running", "to_status": "stopped", "transition_time": "2025-11-09T10:15:00.000Z", "duration_ms": 8320, "triggered_by": "automation_script" } ], "pagination": { "total": 47, "page": 1, "limit": 10, "totalPages": 5 } }}What you learn:
- When states changed
- How long transitions took
- Who/what triggered the change
- Complete audit trail
Status log queries
Section titled “Status log queries”1. Debugging:
# Check if container had recent failuresGET /api/v1/containers/{id}/status-logs?sort_order=desc&limit=20
# Look for: failed states, unexpected stops, slow starts2. Performance analysis:
# Analyze startup timesGET /api/v1/containers/{id}/status-logs
# Check duration_ms for "stopped → running" transitions# Optimize if consistently slow3. Audit trail:
# Who stopped the production container?GET /api/v1/containers/{prod_id}/status-logs
# Check triggered_by field for user/automation identificationOperation timing
Section titled “Operation timing”| Operation | Typical Duration | What Happens |
|---|---|---|
| Start | 5-15 seconds | Boot processes, start services |
| Stop | 5-10 seconds | Graceful shutdown, cleanup |
| Force-Stop | <1 second | Immediate kill |
| Restart | 10-25 seconds | Stop + Start |
| Pause | ~1 second | Freeze all processes |
| Resume | 1-2 seconds | Unfreeze, continue execution |
Factors affecting timing:
- Container image complexity
- Number of running processes
- Allocated resources
- Hoody Kit services (more services = slightly longer)
- Server load
Multi-container operations
Section titled “Multi-container operations”Bulk operations
Section titled “Bulk operations”Start all project containers:
// Get all containers in projectconst response = await fetch( `https://api.hoody.com/api/v1/projects/${projectId}/containers`, { headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` }});
const containers = await response.json();
// Start all stopped containersfor (const container of containers.data.containers) { if (container.status === 'stopped') { await fetch( `https://api.hoody.com/api/v1/containers/${container.id}/start`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` } } ); console.log(`Started: ${container.name}`); }}Conditional operations
Section titled “Conditional operations”Stop containers based on criteria:
// Stop all paused containers (free up RAM).// The list endpoint takes no status filter — filter client-side on the returned array.const { data } = await fetch( 'https://api.hoody.com/api/v1/containers', { headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` }}).then(r => r.json());
const paused = data.containers.filter(c => c.status === 'paused');
for (const container of paused) { await fetch( `https://api.hoody.com/api/v1/containers/${container.id}/stop`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` } } );}Parallel operations
Section titled “Parallel operations”Restart multiple containers simultaneously:
const containerIds = [ '890abcdef12345678901cdef', '901bcdef12345678901cdefa', '012cdef123456789abcdef01'];
// Restart all in parallelawait Promise.all( containerIds.map(id => fetch(`https://api.hoody.com/api/v1/containers/${id}/restart`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HOODY_TOKEN}` } }) ));
console.log('All containers restarted');Autostart configuration
Section titled “Autostart configuration”Autostart controls whether a container starts by itself when its server boots.
Enable autostart
Section titled “Enable autostart”# Enable autostarthoody containers update $CONTAINER_ID --autostart// Enable autostartawait client.api.containers.update(CONTAINER_ID, { autostart: true });# Enable autostartcurl -X PATCH "https://api.hoody.com/api/v1/containers/{id}" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"autostart": 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
Flips CONTAINER_ID’s autostart flag without touching its running state.
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&method=PATCH&bearer_token=TOKEN&json={"autostart":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.
The container then starts automatically when:
- The server reboots
- Server maintenance completes
- The server recovers from a power failure
When to use autostart
Section titled “When to use autostart”Enable autostart for:
- Production services (must be always available)
- Critical infrastructure (databases, APIs)
- Monitoring containers (must run continuously)
- Automation containers (CI/CD, cron jobs)
Disable autostart for:
- Development environments (start manually when needed)
- Testing containers (ephemeral)
- Resource-intensive containers (start on-demand)
- Temporary/experimental containers
Real-world scenarios
Section titled “Real-world scenarios”New code deployment
Section titled “New code deployment”# 1. Snapshot current state (safety)hoody snapshots create --container $CONTAINER_ID --alias "pre-deploy-2025-11-09"
# 2. Access terminal to deploy, then deploy code
# 3. Restart container for clean statehoody containers manage $CONTAINER_ID restart
# 4. Verify services are runninghoody containers get $CONTAINER_ID --runtime true// 1. Snapshot current state (safety)await client.api.containers.createSnapshot(CONTAINER_ID, { alias: 'pre-deploy-2025-11-09'});
// 2. Deploy code via terminal...
// 3. Restart container for clean stateawait client.api.containers.manage(CONTAINER_ID, 'restart');
// 4. Verify services are runningconst status = await client.api.containers.get(CONTAINER_ID, { runtime: 'true' });console.log(status.data);# 1. Snapshot current state (safety)curl -X POST "https://api.hoody.com/api/v1/containers/{id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "pre-deploy-2025-11-09"}'
# 2. Access terminal to deploy# https://{project}-{container}-terminal-1.{server}.containers.hoody.com
# 3. Restart container for clean statecurl -X POST "https://api.hoody.com/api/v1/containers/{id}/restart" \ -H "Authorization: Bearer $HOODY_TOKEN"
# 4. Verify services are runningcurl "https://api.hoody.com/api/v1/containers/{id}?runtime=true" \ -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
Three links for the deploy sequence: snapshot, restart, then check runtime status. Route them through a different running container’s curl-1 — the restart briefly takes down the very service that would carry this request.
# 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-2025-11-09"}&response=transparent
# Restart
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/restart&method=POST&bearer_token=TOKEN&response=transparent
# Check runtime status
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?runtime=true&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.
If deployment fails: Restore snapshot to roll back.
Scheduled maintenance
Section titled “Scheduled maintenance”// Automated maintenance scriptasync function performMaintenance(containerId) { const token = process.env.HOODY_TOKEN; const headers = { 'Authorization': `Bearer ${token}` };
// 1. Stop container await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/stop`, { method: 'POST', headers } );
// 2. Wait for stopped await waitForStatus(containerId, 'stopped');
// 3. Update resources await fetch( `https://api.hoody.com/api/v1/containers/${containerId}`, { method: 'PATCH', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ environment_vars: { "UPDATED": "true" } }) } );
// 4. Restart await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/start`, { method: 'POST', headers } );
// 5. Verify running const status = await fetch( `https://api.hoody.com/api/v1/containers/${containerId}`, { headers } ).then(r => r.json());
return status.data.status === 'running';}
// Run at 2 AM via cronperformMaintenance('890abcdef12345678901cdef');Resource conservation
Section titled “Resource conservation”Pause containers during low-usage periods:
# List containers (filter by status client-side)hoody containers list
# Pause each non-critical containerhoody containers manage $CONTAINER_ID pause
# Morning: Resume all pausedhoody containers listhoody containers manage $CONTAINER_ID resume// List running containersconst running = await client.api.containers.list();
// Pause non-critical containersfor (const c of running.data.containers) { if (c.status === 'running' && !critical.includes(c.name)) { await client.api.containers.manage(c.id, 'pause'); }}
// Morning: Resume all pausedconst paused = await client.api.containers.list();for (const c of paused.data.containers) { if (c.status === 'paused') { await client.api.containers.manage(c.id, 'resume'); }}# List all containers (no server-side status filter; filter client-side)curl "https://api.hoody.com/api/v1/containers" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Pause each non-critical containercurl -X POST "https://api.hoody.com/api/v1/containers/{id}/pause" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Morning: list again, filter client-side for status == "paused"curl "https://api.hoody.com/api/v1/containers" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Resume eachcurl -X POST "https://api.hoody.com/api/v1/containers/{id}/resume" \ -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
Links for listing containers, pausing one, then resuming it. Route them through a different running container’s curl-1: a paused container can’t serve the request that resumes it.
# List containers
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers&method=GET&bearer_token=TOKEN&response=transparent
# Pause
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/pause&method=POST&bearer_token=TOKEN&response=transparent
# Resume
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/resume&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.
Result: Paused containers consume no CPU on your server. Their frozen state stays in RAM until you resume them.
Blue-green deployment
Section titled “Blue-green deployment”# 1. Start green containerhoody containers manage $GREEN_ID start
# 2. Verify green is healthyhoody containers get $GREEN_ID --runtime true
# 3. Re-point alias to green (delete + recreate; container_id is immutable)hoody proxy delete $ALIAS_IDhoody proxy create --container-id $GREEN_ID \ --alias "prod" --program "http" --port 3000 --target-path "/"
# 4. Monitor green in production
# 5. After verification, stop bluehoody containers manage $BLUE_ID stop// 1. Start green containerawait client.api.containers.manage(GREEN_ID, 'start');
// 2. Verify green is healthyconst green = await client.api.containers.get(GREEN_ID, { runtime: 'true' });console.log(green.data.status); // 'running'
// 3. Re-point alias to green (delete + recreate; container_id is immutable)await client.api.proxyAliases.delete(ALIAS_ID);await client.api.proxyAliases.create({ container_id: GREEN_ID, alias: 'prod', program: 'http', port: 3000});
// 4. After verification, stop blueawait client.api.containers.manage(BLUE_ID, 'stop');# 1. Start green containercurl -X POST "https://api.hoody.com/api/v1/containers/{green_id}/start" \ -H "Authorization: Bearer $HOODY_TOKEN"
# 2. Verify green is healthycurl "https://api.hoody.com/api/v1/containers/{green_id}?runtime=true" \ -H "Authorization: Bearer $HOODY_TOKEN"
# 3. Re-point alias to green (delete + recreate; container_id is immutable)curl -X DELETE "https://api.hoody.com/api/v1/proxy/aliases/{alias_id}" \ -H "Authorization: Bearer $HOODY_TOKEN"
curl -X POST "https://api.hoody.com/api/v1/proxy/aliases" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "container_id": "{green_id}", "alias": "prod", "program": "http", "port": 3000, "target_path": "/" }'
# 4. After verification, stop bluecurl -X POST "https://api.hoody.com/api/v1/containers/{blue_id}/stop" \ -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
Links for the cutover: start green, confirm it’s healthy, swap the alias, then stop blue. Route them through a third running container’s curl-1 — green isn’t up yet for the first call, and blue is stopped by the last one.
# Start green
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/GREEN_ID/start&method=POST&bearer_token=TOKEN&response=transparent
# Verify green is healthy
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/GREEN_ID?runtime=true&method=GET&bearer_token=TOKEN&response=transparent
# Delete alias
https://PROJECT_ID-OTHER_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
# Create alias
https://PROJECT_ID-OTHER_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":"GREEN_ID","alias":"prod","program":"http","port":3000,"target_path":"/"}&response=transparent
# Stop blue
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/BLUE_ID/stop&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.
Monitor container operations
Section titled “Monitor container operations”Check current status
Section titled “Check current status” Key fields:
status: Current state (running, stopped, paused)updated_at: Last modification time
Get runtime information
Section titled “Get runtime information” Shows:
- Active terminal sessions
- Display connections
- Running services (PIDs, ports)
- Network services
- Memory/CPU usage
Track operations over time
Section titled “Track operations over time” Analyze:
- Frequency of restarts (stability indicator)
- Downtime duration
- Who triggered changes
- Operation timing patterns
Best practices
Section titled “Best practices”Snapshot before risky operations
Section titled “Snapshot before risky operations”# Before forcing stop or major changesPOST /api/v1/containers/{id}/snapshots{"alias": "before-maintenance"}Prefer graceful stop to force-stop
Section titled “Prefer graceful stop to force-stop”# Preferred (gives processes time to cleanup)POST /api/v1/containers/{id}/stop
# Last resort only (can corrupt data)POST /api/v1/containers/{id}/force-stopVerify that operations complete
Section titled “Verify that operations complete”# Don't assume success - verifyasync function stopContainer(id) { await fetch(`https://api.hoody.com/api/v1/containers/${id}/stop`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } });
// Poll until stopped let attempts = 0; while (attempts < 30) { const status = await fetch( `https://api.hoody.com/api/v1/containers/${id}`, { headers: { 'Authorization': `Bearer ${token}` }} ).then(r => r.json());
if (status.data.status === 'stopped') { return true; }
await new Promise(r => setTimeout(r, 1000)); attempts++; }
throw new Error('Container did not stop in time');}Document operation triggers
Section titled “Document operation triggers”# Add context to operations via comments# (The API has no comment field on operations, so record the reason elsewhere)
# In your automation scripts:// Stopping container for scheduled backup - see RUNBOOK.md section 4.2await stopContainer(prodId);Coordinate on shared containers
Section titled “Coordinate on shared containers”Before stopping shared containers:
- Notify team via chat/email
- Check who’s connected:
GET /api/v1/containers/{id}?runtime=true - Look at
runtime_info.displays.connected_clientsandruntime_info.terminals - Schedule during off-hours when possible
See: Multiplayer by Default for collaboration context.
Useful questions
Section titled “Useful questions”Can I start multiple containers simultaneously?
Section titled “Can I start multiple containers simultaneously?”Yes. Operations are independent HTTP requests, so you can start 100 containers in parallel:
await Promise.all( containerIds.map(id => fetch(`https://api.hoody.com/api/v1/containers/${id}/start`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }) ));What happens to service URLs when a container is stopped?
Section titled “What happens to service URLs when a container is stopped?”Every service URL returns a connection error or 503 Service Unavailable. The URLs still exist; the services behind them are not running. After a restart, the same URLs work again.
Can I pause a container, then update its configuration?
Section titled “Can I pause a container, then update its configuration?”Yes for most fields. PATCH /api/v1/containers/{id} accepts the name, autostart, environment variables, and SSH key in any state, including paused. Two limits apply: narrowing ramdisk_scope is rejected while the container is running, and snapshots cannot be created while it is paused.
Environment-variable changes generally do not require a restart. After a successful live sync, new exec and console processes see the updated values immediately, and the dedicated containers env endpoints also rewrite /etc/environment so new SSH and console logins pick them up. Already-running processes see the new values only after they re-exec.
Does autostart work across server reboots?
Section titled “Does autostart work across server reboots?”Yes. When your server restarts (maintenance, updates, power cycle), all containers with autostart: true start automatically once the server is back online.
Can I be notified when operations complete?
Section titled “Can I be notified when operations complete?”Not directly via the API. Poll the container status or status-logs endpoints, or run the hoody-notifications service inside the container to send an alert when an operation finishes.
What if I stop a container someone is using?
Section titled “What if I stop a container someone is using?”Every connection terminates immediately: terminal sessions close, displays disconnect, and in-flight file operations fail. Coordinate with other users before stopping a shared container, and consider the multiplayer implications.
Do paused containers count toward resource quotas?
Section titled “Do paused containers count toward resource quotas?”A paused container keeps its frozen state in the server’s RAM and its disk usage unchanged, and it still counts toward any container-count limit. Only its CPU goes quiet.
Can containers auto-restart if they crash?
Section titled “Can containers auto-restart if they crash?”Not automatically via the API. Implement monitoring that checks status and restarts if needed. Or use systemd inside containers for process-level auto-restart. Or configure autostart: true for server boot recovery.
What’s the difference between restart and stop+start?
Section titled “What’s the difference between restart and stop+start?”Functionally identical. restart is a convenience endpoint that does the stop and the start in one call: same total time, same result, one request instead of two.
Troubleshooting
Section titled “Troubleshooting”Container won’t start
Section titled “Container won’t start”Problem: Start operation fails or container stuck in “creating”
Solutions:
-
Check status logs:
Terminal window GET /api/v1/containers/{id}/status-logs?limit=5&sort_order=desc# Look for error messages in metadata -
Verify server is available:
Terminal window GET /api/v1/servers/{server_id}# Check: status should be "ready", not "maintenance" -
Check resource availability:
- Server may be at capacity
- Try reducing allocated resources
- Or move to different server via copy
-
Look for image issues:
Terminal window GET /api/v1/containers/{id}# Check container_image is valid
Graceful stop hangs
Section titled “Graceful stop hangs”Problem: Stop operation doesn’t complete
Cause: Container processes not responding to SIGTERM
Solutions:
-
Retry the graceful stop:
Terminal window POST /api/v1/containers/{id}/stop -
Force stop if necessary:
Terminal window POST /api/v1/containers/{id}/force-stop -
Check what’s running:
Terminal window # SSH or terminal into containerps aux# Identify stuck processes
Resume fails after pause
Section titled “Resume fails after pause”Problem: Resume operation returns error
Causes and solutions:
-
Server rebooted during pause:
- Paused state is lost on server reboot
- Solution: Start container instead of resume
-
RAM pressure:
- Server may have deallocated paused container memory
- Solution: Start fresh (state lost)
Operations return 400 (Bad Request)
Section titled “Operations return 400 (Bad Request)”Problem: Valid operation returns 400 error
Check operation validity:
| Current State | Valid Operations |
|---|---|
running | stop, force-stop, restart, pause |
stopped | start, restart, delete |
paused | resume, stop, force-stop |
creating | (none - wait for completion) |
failed | delete |
The API’s own gate is coarse: the container must be in running, stopped, or paused to be managed at all. Finer refusals (starting an already-running container, for instance) come from the host.
Common mistakes:
- Starting an already running container
- Stopping an already stopped container
- Pausing a stopped container
- Resuming a running container
Solution: Check current status first:
GET /api/v1/containers/{id}# Verify status before operationService URLs fail after start
Section titled “Service URLs fail after start”Problem: Container status shows “running” but service URLs fail
Debug steps:
-
Wait for services to initialize:
- Status changes to “running” before all services are ready
- Wait 30-60 seconds after start
-
Check runtime info:
Terminal window GET /api/v1/containers/{id}?runtime=true# Verify services appear in runtime_info.services# Check for "active" status -
Verify hoody_kit is enabled:
Terminal window GET /api/v1/containers/{id}# Check: "hoody_kit": true -
Check specific service:
Terminal window # SSH or terminal into containersystemctl status hoody-terminalsystemctl status hoody-display# etc.
Container restarts unexpectedly
Section titled “Container restarts unexpectedly”Problem: Container restarts unexpectedly
Check causes:
-
Autostart enabled + server rebooted:
Terminal window GET /api/v1/containers/{id}# Check: "autostart": true -
Check status logs for pattern:
Terminal window GET /api/v1/containers/{id}/status-logs?limit=20# Look for frequent "stopped → running" transitions# Check triggered_by field -
Process-level crashes:
- Container OS auto-restarts
- Check logs inside container
- Or configure alerting via hoody-notifications
What’s next
Section titled “What’s next”Container operations:
- Snapshots → - Capture state before operations, restore if needed
- Copy & Sync → - Duplicate containers for redundancy
- Images → - Choose the OS your workload needs
Operational topics:
- Network Configuration → - Route through proxies
- Firewall → - Control traffic
- Hoody Kit Services → - Use the HTTP services
What this page covered:
- Containers transition between states via HTTP operations
- Stop and start give a clean runtime state or restart configured services; they are not a general environment-variable propagation mechanism
- Pause and resume handle quick suspension
- Force-stop belongs to emergencies only
- Status logs record every transition
- Autostart controls boot behavior
Try it out
Section titled “Try it out”Test container operations from your browser:
Manage container lifecycle - set operation to: start, stop, force-stop, restart, pause, or resume
Path Parameters
Authentication
Use the authentication widget in the header to login or set an API token
Authentication: Authorization: Bearer header (auto-attached for trusted domains)