Snapshots
Section titled “Snapshots”A snapshot records a container’s filesystem at a point in time. Restoring one puts files, packages, and on-disk data back as they were, so an unwanted change costs a single API call to undo.
Once you can create and manage containers, snapshots are how you keep a known-good state to fall back on and how you mark versions of a machine you can return to.
API Endpoints Summary
Section titled “API Endpoints Summary”This Foundation page explains snapshot concepts and workflows. The endpoint reference documents request and response shapes in full.
Snapshot operations:
- GET /api/v1/containers/{id}/snapshots - List all snapshots
- POST /api/v1/containers/{id}/snapshots - Create new snapshot
- PUT /api/v1/containers/{id}/snapshots/{name} - Restore from snapshot
- DELETE /api/v1/containers/{id}/snapshots/{name} - Delete snapshot
- PUT /api/v1/containers/{id}/snapshots/{name}/alias - Update snapshot alias
Related:
- GET /api/v1/containers/{id} - Container details
- POST /api/v1/containers/{id}/copy - Copy a container (optionally from a specific snapshot)
Prerequisite:
$HOODY_TOKEN. The HTTP examples on this page assumeHOODY_TOKENis exported to a valid Hoody bearer token. Create a long-lived automation token (hdy_…) via the token flow, thenexport HOODY_TOKEN=hdy_…before running the curl snippets.
How snapshots work
Section titled “How snapshots work”A snapshot records everything on a container’s disk at a specific moment:
Yesterday ──→ 3 hours ago ──→ 1 hour ago ──→ NOW ● ● ● ●Each point on that line is a snapshot, and restoring one returns the disk to that moment:
- The entire filesystem, every file exactly as it was
- Database files, holding the data written to disk at that moment
- Configuration and environment files on disk
- Installed software, including packages and dependencies
Snapshots use copy-on-write (CoW), so creating one writes filesystem metadata instead of duplicating the filesystem. Creation is close to instant, rollback is correspondingly fast, and each snapshot after the first costs only the blocks that changed since the last one.
When to use snapshots
Section titled “When to use snapshots”An AI agent can rewrite more code than you have time to read before it runs. Take a snapshot first and the change is reversible: if the result is broken, restore and the disk is back where it was. The same holds for a deployment you are not certain about, and for experiments you expect to throw away.
Snapshot before a risky change:
If something breaks, restore the snapshot:
Snapshot before a deployment:
# Production deployment in progressPOST /api/v1/containers/{prod_id}/snapshots{"alias": "pre-deploy-2025-11-09-14-30"}
# Deploy...
# Issues in production? Restore by the snapshot's `name`# (the sanitized alias supplied at creation is that name)PUT /api/v1/containers/{prod_id}/snapshots/pre-deploy-2025-11-09-14-30
# Back to working state instantlySnapshot creation
Section titled “Snapshot creation”The create call
Section titled “The create call” Response:
{ "statusCode": 200, "message": "Snapshot created successfully", "data": { "container_id": "890abcdef12345678901cdef", "project_id": "67e89abc123def456789abcd", "snapshot": { "name": "production-stable", "alias": "production-stable", "created_at": "2025-11-09T14:30:45.000Z", "last_used_at": null, "expires_at": "2026-02-07T14:30:45.000Z", "stateful": false, "size": 4589764321 } }}The call returns as soon as the snapshot exists, whether the container is running or stopped.
Snapshot naming
Section titled “Snapshot naming”Auto-generated names (no alias sent at creation):
- Format:
snap-YYYYMMDD-HHMMSS - Example:
snap-20251109-143045 - Unique, chronologically sortable
Aliases sent at creation become the name:
- The
aliasyou pass to the create call is sanitized to[a-zA-Z0-9_-](leading dashes removed) and used as the snapshot’sname, so"My Backup"lands asMyBackup - An alias that sanitizes to an empty string falls back to the timestamp form
- Max 100 characters
- Example:
"pre-deploy","working-state","before-ai-changes" - Setting an alias after creation (the alias update call) does not rename the snapshot. The
namestays whatever it was at creation
Always take the restore/delete key from the name field returned by the list call.
Snapshot expiration
Section titled “Snapshot expiration”Pass expiry in days and the snapshot deletes itself when the term is up:
# Expires in 30 daysPOST /api/v1/containers/{id}/snapshots{"alias": "temp-backup", "expiry": 30}
# Expires in 90 daysPOST /api/v1/containers/{id}/snapshots{"alias": "quarterly-backup", "expiry": 90}
# Never expires (omit the `expiry` field entirely)POST /api/v1/containers/{id}/snapshots{"alias": "permanent-baseline"}Once a snapshot expires it is deleted, its storage is freed, and it can no longer be restored. That makes expiry a good fit for temporary experiment backups, pre-deployment snapshots you plan to discard after verification, and any backup rotation you would otherwise clean up by hand.
Snapshot restore
Section titled “Snapshot restore”The restore call
Section titled “The restore call” What happens:
- The container’s current filesystem is discarded
- The snapshot’s filesystem takes its place, including on-disk configuration
- The container starts fresh from that filesystem, and processes do not resume mid-execution
Restoration time: This depends on the snapshot’s size and on how much the disk has changed since it was taken. Small restores finish in seconds; very large ones can take many minutes.
A safe restore sequence
Section titled “A safe restore sequence”# Capture "right now" before restoringcurl -X POST "https://api.hoody.com/api/v1/containers/{id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "before-restore"}'Now you can restore to either state.
# Restore to previous snapshot (use the exact `name` returned by create/list)curl -X PUT "https://api.hoody.com/api/v1/containers/{id}/snapshots/{snapshot_name_from_list}" \ -H "Authorization: Bearer $HOODY_TOKEN"# Check container statuscurl "https://api.hoody.com/api/v1/containers/{id}" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Confirm status == "running" (or whatever state matches the snapshot)# Access via service URLs# https://{project}-{container}-terminal-1.{server}.containers.hoody.com
# Verify everything works as expected# If issues, restore to "before-restore" snapshotSnapshot listing
Section titled “Snapshot listing” Response:
{ "statusCode": 200, "message": "Snapshots retrieved successfully", "data": { "container_id": "890abcdef12345678901cdef", "project_id": "67e89abc123def456789abcd", "snapshots": [ { "name": "production-stable", "alias": "production-stable", "created_at": "2025-11-09T14:30:45.000Z", "last_used_at": "2025-11-09T16:15:00.000Z", "expires_at": "2026-02-07T14:30:45.000Z", "stateful": false, "size": 4589764321 }, { "name": "before-ai-refactor", "alias": "before-ai-refactor", "created_at": "2025-11-08T10:00:00.000Z", "last_used_at": null, "expires_at": "2025-12-07T10:00:00.000Z", "stateful": false, "size": 4123456789 } ] }}Fields returned:
name- The restore/delete key: the sanitized alias supplied at creation, or an auto-generatedsnap-YYYYMMDD-HHMMSSwhen no alias was supplied. In the two examples above, the creation aliases became their names.alias- Your friendly name; can be set or changed later without renaming the snapshotcreated_at- When snapshot was takenlast_used_at- When last used for restore/copy (null if never used)expires_at- Auto-deletion date (null if permanent)stateful- Whether a RAM/process state was captured. Hoody snapshots are filesystem-only, so this is alwaysfalse.size- Storage space used (in bytes)
What snapshots capture
Section titled “What snapshots capture”A snapshot is stateless, meaning filesystem-only, which is why the call works in either container state:
# Works whether the container is running or stoppedPOST /api/v1/containers/{id}/snapshotsIncludes:
- Filesystem (everything written to disk)
Does not include:
- Running processes
- RAM / memory state
- In-flight network connections
The stateful field on every snapshot is false, because Hoody snapshots do not capture a RAM dump.
Restoration: The container’s filesystem reverts to the snapshot, then the container starts fresh from that disk state. Processes do not resume mid-execution, and anything that was only in memory at snapshot time is not restored.
Snapshot deletion
Section titled “Snapshot deletion” Deleting a snapshot removes the captured filesystem state and its metadata (alias, timestamps) and frees the storage. It cannot be undone, and the snapshot’s name stops resolving.
When to delete a snapshot
Section titled “When to delete a snapshot”Delete one once it is a spent temporary backup, once a newer snapshot supersedes it, once an old experiment branch is finished, or whenever you need the storage back.
Keep one when it is your long-term disaster-recovery point, when compliance or audit rules require it, when it serves as a template for new containers, or when it records an architecture decision or configuration you may want to look up later.
Snapshot strategies
Section titled “Snapshot strategies”Pre-operation safety
Section titled “Pre-operation safety”Take a snapshot before every change you might want to undo:
# Before AI code generationPOST /api/v1/containers/{id}/snapshots{"alias": "before-ai-gen-${timestamp}"}
# Before manual configurationPOST /api/v1/containers/{id}/snapshots{"alias": "before-nginx-config"}
# Before dependency updatesPOST /api/v1/containers/{id}/snapshots{"alias": "before-npm-update"}If anything breaks, restore the snapshot you took just before the change.
Versioned milestones
Section titled “Versioned milestones”Snapshot the states you may want to return to:
# Working features (permanent, no expiry field)POST /api/v1/containers/{id}/snapshots{"alias": "v1.0.0-stable"}
# Before major refactorPOST /api/v1/containers/{id}/snapshots{"alias": "v1.0.0-before-refactor", "expiry": 90}
# After refactor complete (permanent, no expiry field)POST /api/v1/containers/{id}/snapshots{"alias": "v2.0.0-stable"}Over time this gives the container itself a version history.
Daily automated backups
Section titled “Daily automated backups”// Automated snapshot script (run via cron)async function dailyBackup(containerId) { const token = process.env.HOODY_TOKEN; const date = new Date().toISOString().split('T')[0];
await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/snapshots`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ alias: `daily-backup-${date}`, expiry: 30 // Keep 30 days of dailies }) } );
console.log(`Backup created: daily-backup-${date}`);}
// Run at 2 AM dailydailyBackup('890abcdef12345678901cdef');With expiry set, old snapshots delete themselves and there is no cleanup job to run.
Branch points
Section titled “Branch points”Snapshots let you branch a container the way Git branches a repository: keep a main line, take a snapshot before an experiment, then either promote the result or restore the main one.
# Main production statePOST /api/v1/containers/{prod_id}/snapshots{"alias": "prod-main"}
# Experiment: try a new featurePOST /api/v1/containers/{prod_id}/snapshots{"alias": "experiment-feature-x"}
# Work on feature...
# Feature works, so make it the new mainPOST /api/v1/containers/{prod_id}/snapshots{"alias": "prod-main-v2"}
# If the feature failed, restore the prod-main snapshot (by its `name`)PUT /api/v1/containers/{prod_id}/snapshots/prod-mainA single container can hold as many of these stored states as you keep snapshots for.
Snapshot operations
Section titled “Snapshot operations”List all snapshots
Section titled “List all snapshots” Returns the snapshots in chronological order, with details for each.
Create snapshot
Section titled “Create snapshot” Both fields are optional. Omit alias and the name is auto-generated. Omit expiry and the snapshot is permanent.
Restore snapshot
Section titled “Restore snapshot” Use the snapshot’s name, as returned by GET /api/v1/containers/{id}/snapshots. Create a snapshot with no alias and the name is auto-generated as snap-YYYYMMDD-HHMMSS. Pass an alias at creation and that alias, stripped to [a-zA-Z0-9_-] with leading dashes removed, becomes the name (an alias that sanitizes to nothing falls back to the timestamp form). Never assume the format: list the snapshots first and pass the exact name field to the restore call.
Delete snapshot
Section titled “Delete snapshot” Storage is freed immediately.
Snapshot size and storage
Section titled “Snapshot size and storage”A snapshot’s size tracks the container’s on-disk usage, since no RAM dump is stored:
| Container Disk Usage | First Snapshot Size |
|---|---|
| 10 GB filesystem | ~10 GB |
| 50 GB filesystem | ~50 GB |
| 100 GB filesystem | ~100 GB |
Only the first snapshot costs that much. Later ones are incremental, and Hoody handles that without configuration: each stores the blocks that changed since the previous snapshot, so it costs far less than a full copy. Storage is billed either way, so delete snapshots you no longer need.
Snapshots vs container copy
Section titled “Snapshots vs container copy”Snapshots
Purpose: Roll a container back to an earlier disk state
Characteristics:
- Instant creation (seconds)
- Incremental storage
- Built into container
- Fast restoration
- Tied to source container
- Cannot run independently
- Deleted with the container (cascade delete)
Use for:
- Backup/restore
- Experimenting safely
- Rollback mechanism
- Version milestones
Container Copy
Purpose: Duplicate entire container
Characteristics:
- Independent container
- Can run on different server
- Can be in different project
- Survives source deletion
- Slower creation (minutes)
- Full storage (no incremental)
- More resources required
Use for:
- Creating staging from prod
- Disaster recovery (different server)
- Team environments (same setup)
- Production redundancy
A copy can start from a specific snapshot instead of the container’s live disk:
# Create container from specific snapshotPOST /api/v1/containers/{source_id}/copy{ "target_project_id": "{project_id}", "name": "restored-copy", "source_snapshot": "snap-20251109-143045"}See: Copy & Sync for duplication workflows.
Real-world scenarios
Section titled “Real-world scenarios”Safe AI experimentation
Section titled “Safe AI experimentation”# 1. Snapshot current working statehoody snapshots create --container $DEV_ID --alias "working-baseline"
# 2. Let AI agent make changes...
# 3. If the changes work, create a new milestonehoody snapshots create --container $DEV_ID --alias "with-ai-improvements"
# If they broke something, restore the baseline# (use the exact `name` returned by create/list; when supplied at creation,# the sanitized alias is that name)# `-y` skips the destructive-operation confirmation prompt, required in scripts and agentshoody snapshots restore --container $DEV_ID --name working-baseline -y// 1. Snapshot current working stateawait client.api.containers.createSnapshot(DEV_ID, { alias: 'working-baseline' });
// 2. Let AI agent make changes...
// 3. If the changes work, create a new milestoneawait client.api.containers.createSnapshot(DEV_ID, { alias: 'with-ai-improvements' });
// If they broke something, restore the baseline// (use the exact `name` returned by create/list; when supplied at creation,// the sanitized alias is that name)await client.api.containers.restoreSnapshot(DEV_ID, 'working-baseline');# 1. Snapshot current working statecurl -X POST "https://api.hoody.com/api/v1/containers/{dev_id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "working-baseline"}'
# 2. Let AI agent make changes...
# 3. If the changes work, create a new milestonecurl -X POST "https://api.hoody.com/api/v1/containers/{dev_id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "with-ai-improvements"}'
# If they broke something, restore the baseline# (use the exact `name` returned by create/list; when supplied at creation,# the sanitized alias is that name)curl -X PUT "https://api.hoody.com/api/v1/containers/{dev_id}/snapshots/working-baseline" \ -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
Snapshot before letting the agent change the container, snapshot again once its changes look good, or restore the baseline link if they didn’t.
# Snapshot the baseline
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/DEV_ID/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"working-baseline"}&response=transparent
# Create a milestone
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/DEV_ID/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"with-ai-improvements"}&response=transparent
# Restore the baseline
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/DEV_ID/snapshots/working-baseline&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.
The agent can break things in the container and the baseline is still there.
Production deploy with rollback
Section titled “Production deploy with rollback”# Before deploymenthoody snapshots create --container $PROD_ID \ --alias "pre-deploy-v2.1.0" --expiry 30
# Deploy via terminal/SSH...
# Issue detected, so roll back (use the exact `name` returned by create/list; when# supplied at creation, the sanitized alias is that name, and the dots are stripped,# so "pre-deploy-v2.1.0" lands as "pre-deploy-v210")# `-y` skips the destructive-operation confirmation prompt, required in scripts and agentshoody snapshots restore --container $PROD_ID --name pre-deploy-v210 -y
# Production rolled back to the snapshot state// Before deploymentawait client.api.containers.createSnapshot(PROD_ID, { alias: 'pre-deploy-v2.1.0', expiry: 30 });
// Deploy via terminal...
// Issue detected, so roll back (use the exact `name` returned by create/list; when// supplied at creation, the sanitized alias is that name, and the dots are stripped,// so 'pre-deploy-v2.1.0' lands as 'pre-deploy-v210')await client.api.containers.restoreSnapshot(PROD_ID, 'pre-deploy-v210');// Production restored in 15 seconds# Before deploymentcurl -X POST "https://api.hoody.com/api/v1/containers/{prod_id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "pre-deploy-v2.1.0", "expiry": 30}'
# Deploy via terminal/SSH# https://{project}-{prod_id}-terminal-1.{server}.containers.hoody.com
# Issue detected, so roll back (use the exact `name` returned by create/list; when# supplied at creation, the sanitized alias is that name, and the dots are stripped,# so "pre-deploy-v2.1.0" lands as "pre-deploy-v210")curl -X PUT "https://api.hoody.com/api/v1/containers/{prod_id}/snapshots/pre-deploy-v210" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Production restored in 15 secondsOne 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
Snapshot before the deploy, then use the roll-back link if an issue turns up afterward — its target name is the alias with dots stripped, as the create call sanitizes it.
# Pre-deploy snapshot
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/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"pre-deploy-v2.1.0","expiry":30}&response=transparent
# Roll back
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/snapshots/pre-deploy-v210&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.
Development checkpoints
Section titled “Development checkpoints”# Working on complex feature over days# Create checkpoints at milestones
# Day 1: Database schema readyPOST /api/v1/containers/{dev_id}/snapshots{"alias": "checkpoint-db-schema", "expiry": 7}
# Day 2: API endpoints completePOST /api/v1/containers/{dev_id}/snapshots{"alias": "checkpoint-api-done", "expiry": 7}
# Day 3: Frontend integratedPOST /api/v1/containers/{dev_id}/snapshots{"alias": "checkpoint-frontend", "expiry": 7}
# If something breaks on day 4, restore the day 3 checkpoint# (use the exact `name` returned by create/list;# when supplied at creation, the sanitized alias is that name)PUT /api/v1/containers/{dev_id}/snapshots/checkpoint-frontendA 7-day expiry clears these checkpoints without any further action.
Container templates
Section titled “Container templates”# Snapshot as permanent templatehoody snapshots create --container $TEMPLATE_ID \ --alias "dev-template-2025"
# When a new developer joins, copy from this snapshothoody containers copy $TEMPLATE_ID \ --target-project-id $THEIR_PROJECT \ --name "new-dev-env" \ --source-snapshot "dev-template-2025"// Snapshot as permanent template (omit `expiry` for no expiration)await client.api.containers.createSnapshot(TEMPLATE_ID, { alias: 'dev-template-2025' });
// When a new developer joins, copy from this snapshotawait client.api.containers.copy(TEMPLATE_ID, { target_project_id: THEIR_PROJECT, name: 'new-dev-env', source_snapshot: 'dev-template-2025'});# Snapshot as permanent templatecurl -X POST "https://api.hoody.com/api/v1/containers/{template_id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "dev-template-2025"}'
# When a new developer joins, copy from this snapshotcurl -X POST "https://api.hoody.com/api/v1/containers/{template_id}/copy" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "target_project_id": "{their_project}", "name": "new-dev-env", "source_snapshot": "dev-template-2025" }'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
Snapshot a configured container once as a template, then repeat the copy link with each new developer’s project id to hand them their own environment.
# Create template snapshot
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/TEMPLATE_ID/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"dev-template-2025"}&response=transparent
# Copy from template
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/TEMPLATE_ID/copy&method=POST&bearer_token=TOKEN&json={"target_project_id":"THEIR_PROJECT_ID","name":"new-dev-env","source_snapshot":"dev-template-2025"}&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.
One prepared container becomes the starting point for every environment you hand out afterwards.
Snapshot management
Section titled “Snapshot management”List snapshots
Section titled “List snapshots” The returned list is what you drive cleanup from:
- Monitor snapshot accumulation (
response.data.snapshots.length) - Trigger cleanup when count is high
- Verify a specific snapshot exists before restore
Clean up old snapshots
Section titled “Clean up old snapshots”// Delete snapshots older than 30 days (except permanent ones)async function cleanupSnapshots(containerId) { const token = process.env.HOODY_TOKEN; const headers = { 'Authorization': `Bearer ${token}` };
// Get all snapshots const response = await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/snapshots`, { headers } ); const { snapshots } = await response.json().then(r => r.data);
// Find old, non-permanent snapshots const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
for (const snapshot of snapshots) { const createdTime = new Date(snapshot.created_at).getTime(); const isPermanent = snapshot.expires_at === null;
if (!isPermanent && createdTime < thirtyDaysAgo) { await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/snapshots/${snapshot.name}`, { method: 'DELETE', headers } ); console.log(`Deleted old snapshot: ${snapshot.alias || snapshot.name}`); } }}Setting expiry at creation avoids the script entirely, since the snapshots delete themselves.
Best Practices
Section titled “Best Practices”Snapshot before destructive operations
Section titled “Snapshot before destructive operations”# Always snapshot before:POST /api/v1/containers/{id}/snapshots
# Then:- force-stop operations- major configuration changes- dependency updates (npm, apt, pip)- database migrations- letting AI modify code- production deploymentsUse descriptive aliases
Section titled “Use descriptive aliases”# Good aliases (context-rich){"alias": "pre-deploy-v2.1.0-2025-11-09"}{"alias": "before-database-migration"}{"alias": "working-state-ai-approved"}
# Poor aliases (vague){"alias": "backup"}{"alias": "test"}{"alias": "snapshot1"}The alias is the only context you get when you have to pick a snapshot to restore months later.
Set an appropriate expiry
Section titled “Set an appropriate expiry”{ "alias": "before-experiment", "expiry": 7}Short experiments: 7 days, then automatic cleanup
{ "alias": "pre-deploy-v2.1.0", "expiry": 30}After deployment verified (30 days): Auto-delete
{ "alias": "v1.0.0-stable"}Major versions: Omit expiry to keep permanently
Quiesce writes before snapshotting
Section titled “Quiesce writes before snapshotting”# Before snapshotting a container with active databases/writes:
# 1. Stop container (or flush/quiesce the workload)POST /api/v1/containers/{id}/stop
# 2. Create snapshot of a quiet, consistent filesystemPOST /api/v1/containers/{id}/snapshots{"alias": "daily-backup"}
# 3. Restart if neededPOST /api/v1/containers/{id}/startA filesystem with no writes in flight restores cleanly and consistently.
Verify the snapshot exists
Section titled “Verify the snapshot exists”# Before relying on restore, verify snapshot existscurl "https://api.hoody.com/api/v1/containers/{id}/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ | grep "production-stable"An old snapshot may have expired or been deleted, so check before you rely on it.
Integration patterns
Section titled “Integration patterns”With container copy
Section titled “With container copy”A copy can read from a snapshot rather than the container’s live disk:
# 1. Snapshot productionPOST /api/v1/containers/{prod_id}/snapshots{"alias": "prod-stable-2025-11-09"}
# 2. Copy to staging from this snapshotPOST /api/v1/containers/{prod_id}/copy{ "target_project_id": "{staging_project}", "name": "staging-env", "source_snapshot": "prod-stable-2025-11-09"}Staging then starts from the exact production state that snapshot captured.
With automated deployment
Section titled “With automated deployment”async function deployWithSafety(containerId, deployScript) { const token = process.env.HOODY_TOKEN; const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' };
// 1. Pre-deploy snapshot const snapshot = await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/snapshots`, { method: 'POST', headers, body: JSON.stringify({ alias: `pre-deploy-${Date.now()}`, expiry: 7 }) } ).then(r => r.json());
try { // 2. Execute deployment await deployScript();
// 3. Health check const health = await checkHealth(containerId);
if (!health.ok) { throw new Error('Health check failed'); }
// 4. Success - create "deployed" snapshot await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/snapshots`, { method: 'POST', headers, body: JSON.stringify({ alias: `deployed-${Date.now()}`, expiry: 30 }) } );
return { success: true };
} catch (error) { // 5. Failure - restore snapshot console.error('Deployment failed, rolling back...', error);
await fetch( `https://api.hoody.com/api/v1/containers/${containerId}/snapshots/${snapshot.data.snapshot.name}`, { method: 'PUT', headers } );
return { success: false, error, rolledBack: true }; }}The rollback path lives inside the deploy function rather than in a runbook someone has to follow under pressure.
Useful Questions
Section titled “Useful Questions”How long does snapshot creation take?
Section titled “How long does snapshot creation take?”Nearly instant. Copy-on-write writes filesystem metadata rather than a second copy of your data. The container keeps running while the snapshot is taken, and only the on-disk state is captured.
Can I snapshot a running container?
Section titled “Can I snapshot a running container?”Yes, and a stopped one. A paused container has to be resumed or stopped first, because the API rejects snapshotting a paused container. Either way the snapshot is filesystem-only: it captures the disk, not running processes or RAM. For the most consistent capture of in-memory data, flush or stop the workload first.
What happens to snapshots when I delete the container?
Section titled “What happens to snapshots when I delete the container?”Snapshots are deleted with the container. Deleting a container permanently deletes all of its snapshots (cascade delete). To keep the state, copy the container first. The copy is an independent container and survives its source.
Can I restore a snapshot to a different container?
Section titled “Can I restore a snapshot to a different container?”Not through the restore call. You can copy a container from a specific snapshot instead, which creates a new container holding that snapshot’s state: pass source_snapshot to the copy call.
How many snapshots can I create?
Section titled “How many snapshots can I create?”Each container has a snapshot cap enforced by the API. On free-tier servers it defaults to 10 per container and is far higher on rented servers, and both figures are operator-tunable. Hitting the cap returns a clear error on the create call. Use expiry to automate cleanup and delete stale snapshots to stay under it.
Do snapshots include proxy aliases and permissions?
Section titled “Do snapshots include proxy aliases and permissions?”No. Snapshots capture container filesystem state only. Proxy aliases and permissions are configured separately at the proxy level. After restoring, you may need to reconfigure aliases.
Can I snapshot multiple containers simultaneously?
Section titled “Can I snapshot multiple containers simultaneously?”Yes. Snapshot creation is a normal HTTP call per container, so you can script it across a fleet. Mind the account-wide write limit: snapshot create, restore, delete, and alias update share one bucket whose ceiling is SNAPSHOT_WRITE_RATE_LIMIT_MAX multiplied by the deployment’s RATE_LIMIT_MAX_MULTIPLIER, which is 50 requests per 5 minutes in the shipped configuration. Batch large fleets through a queue and retry on 429 rather than firing every request at once.
What’s the difference between snapshot and backup?
Section titled “What’s the difference between snapshot and backup?”A snapshot captures state in place; a backup copies data to storage somewhere else. Because the snapshot lives on the same server as its container and is deleted with it, treat it as a rollback point rather than an off-server backup. For disaster recovery, combine snapshots with a container copy onto a different server.
Can snapshots be encrypted?
Section titled “Can snapshots be encrypted?”Snapshots are stored on the server’s disk like the rest of the container filesystem, so they inherit the host’s LUKS full-disk encryption: a snapshot on a stolen or seized drive is ciphertext. That protection ends the moment the host is running and the volume is unlocked, and it does not follow a snapshot copied elsewhere. For sensitive data, encrypt at the application level before snapshotting.
Troubleshooting
Section titled “Troubleshooting”Snapshot creation fails
Section titled “Snapshot creation fails”Problem: Snapshot operation returns error
Solutions:
-
Check storage usage:
Terminal window GET /api/v1/containers/{id}# Verify container has available storage -
Check container status:
Terminal window GET /api/v1/containers/{id}# status should be running or stopped, not failed/creating -
Verify permissions:
- Ensure you own the container
- Check auth token is valid
Snapshot restore hangs
Section titled “Snapshot restore hangs”Problem: Restore operation doesn’t complete
Restore time varies widely with snapshot size and with how far the disk has diverged since it was taken. If it is running longer than you expect:
-
Check snapshot size:
Terminal window GET /api/v1/containers/{id}/snapshots# Large snapshots (>100 GB) take longer -
Wait longer. Very large restores can take many minutes
-
Check server status:
Terminal window GET /api/v1/servers/{id}# Server status should be "active"
Cannot find the snapshot to restore
Section titled “Cannot find the snapshot to restore”Problem: Restore returns 404 Not Found
Solutions:
-
Verify snapshot name (not alias):
Terminal window # List snapshots to get exact nameGET /api/v1/containers/{id}/snapshots# Use the "name" field exactly as returned# It is the sanitized alias you passed at creation, or snap-YYYYMMDD-HHMMSS# if you created the snapshot without one. Never guess the format -
Check snapshot didn’t expire:
Terminal window GET /api/v1/containers/{id}/snapshots# Verify snapshot still in list# Check expires_at hasn't passed
Snapshots consuming too much storage
Section titled “Snapshots consuming too much storage”Problem: Snapshot storage costs are high
Solutions:
-
Delete old/unused snapshots:
Terminal window # List snapshots sorted by ageGET /api/v1/containers/{id}/snapshots# Delete snapshots never used for restoreDELETE /api/v1/containers/{id}/snapshots/{old_snapshot_name} -
Set expiration on new snapshots:
Terminal window POST /api/v1/containers/{id}/snapshots{"alias": "temp-backup", "expiry": 7} -
Trim the container filesystem before snapshotting (clear caches, logs, build artifacts) to reduce snapshot size:
Terminal window # e.g. clean package caches / temp files inside the container, then:POST /api/v1/containers/{id}/snapshots
Restored container is not what you expected
Section titled “Restored container is not what you expected”Problem: After restore, container state doesn’t match memory
Possible causes:
-
Restored wrong snapshot:
- Verify the snapshot name/alias passed to the restore operation
- Re-list snapshots and confirm the intended one was targeted
-
Snapshots are filesystem-only:
- Running processes and in-memory state are never included
- Only the filesystem is restored
- Container starts fresh from that filesystem
-
Post-snapshot changes:
- The snapshot captures only that moment
- Changes made afterwards are not included
- Verify snapshot created_at timestamp
What’s Next
Section titled “What’s Next”Continue with:
- Copy & Sync → - Duplicate containers, sync changes
- Images → - Choose base OS and software
- Create, Edit, Delete → - Container fundamentals
Use snapshots with:
- Managing Containers → - Snapshot before stop/restart operations
- Network Configuration → - Snapshot before network changes
- Firewall → - Snapshot before firewall modifications
Recap:
- Snapshots capture the container’s filesystem (stateless, no RAM/process state)
- Restore reverts the disk to any previous moment, then starts fresh
- Expiration enables automatic cleanup
- Snapshots are cascade-deleted with their container (copy the container first to preserve the state)
- A container copy can be created from a specific snapshot
- Storage is incremental after the first snapshot