Snapshots
Section titled “Snapshots”A snapshot records a container’s entire filesystem at a point in time: every file, database row, config, log, installed package, and environment file. Rather than a diff of selected paths, it is the complete disk state of the machine, frozen at a moment and restorable with one API call.
You do not decide what to include, write migration scripts, or track what changed. A button press or a single API call preserves the entire machine, and another returns it to exactly that state.
What a snapshot captures
Section titled “What a snapshot captures”Every snapshot records the complete filesystem state of a container:
| Component | Captured | What it means |
|---|---|---|
| Filesystem | Files, directories, permission bits | Code, configs, logs, and data, exactly as they were |
| Databases | Data, tables, indexes | SQLite files and PostgreSQL data directories, byte-identical on disk |
| Installed software | apt packages, npm modules, binaries | Restored at their exact versions, nothing to reinstall |
| Environment | Environment files, shell configs, crontabs | The on-disk runtime context is preserved |
| Network config | DNS settings, routing table, proxy configuration | On-disk network configuration is identical after restore |
If it is written to disk in the container, the snapshot captures it.
How snapshots work
Section titled “How snapshots work”Hoody uses Copy-on-Write (CoW) at the filesystem level. When you create a snapshot, Hoody does not copy the entire disk. It marks the current filesystem state as immutable and begins tracking changes. Only new or modified blocks are stored separately.
This means:
- Fast creation. A snapshot marks the current state rather than copying the disk, so creation cost does not grow with container size; nothing is copied or compressed.
- Minimal storage. The first snapshot references the existing filesystem, and each later snapshot stores only the delta. Ten snapshots of a 50GB container do not cost 500GB; they cost 50GB plus the changes.
- Frequent snapshots. The per-snapshot overhead is small enough to snapshot every commit, deployment, or experiment. Per-container snapshot caps apply (higher on rented servers than on the free tier), and expiring snapshots clean themselves up.
- Whole-machine restore. Restoring a snapshot returns the container to the captured state exactly, in one API call, with no migration scripts and no partial state. How long it takes depends on the container.
Snapshot 1 (baseline) ──→ Full filesystem referenceSnapshot 2 (after AI) ──→ Delta: 47 files changedSnapshot 3 (after deploy)──→ Delta: 12 files changedSnapshot 4 (new feature) ──→ Delta: 89 files changed
Total storage: baseline + 148 files of changesNOT: 4 full copies of the filesystemA restore point before AI changes
Section titled “A restore point before AI changes”AI generates code you cannot fully review. An LLM rewrites your authentication module: it looks correct at a glance and passes the tests you thought to write, but a subtle change in how sessions are invalidated slips past code review. Three days later you notice stale sessions. Two days after that, you trace them to the AI’s rewrite, and a week of development now sits on top of the bug.
Without a snapshot, that means reconstructing a week of changes by hand. With one, you restore before-ai-auth-rewrite, compare the two states, fix the specific issue, and move on.
# Before letting AI touch your codehoody snapshots create -c $CONTAINER_ID \ --alias "before-ai-refactor"
# AI makes its changes...# If something breaks, restore# (use the exact `name` returned by create/list; when supplied at creation,# the sanitized alias is that name)hoody snapshots restore -c $CONTAINER_ID --name "before-ai-refactor"
# Back to exactly where you wereimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Snapshot before AI makes changesconst snapshot = await client.api.containers.createSnapshot(containerId, { alias: 'before-ai-refactor'});
// Let AI work...// If it breaks things:await client.api.containers.restoreSnapshot( containerId, snapshot.data.snapshot.name);// Container is exactly as it was before the AI touched it# Snapshot before AI changescurl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "before-ai-refactor"}'
# AI makes changes...
# If something broke, restore# (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/$CONTAINER_ID/snapshots/before-ai-refactor" \ -H "Authorization: Bearer $HOODY_TOKEN"
# When the restore completes: everything is exactly as it wasOne 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
The first link snapshots the container before AI starts; the second restores it if the change turns out wrong. Restore uses the exact name from the create response — it matches the alias here because before-ai-refactor needs no sanitizing.
# Snapshot before AI changes
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/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"before-ai-refactor"}&response=transparent
# Restore if something broke
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/snapshots/before-ai-refactor&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.
Snapshot before every AI interaction. Creating one takes seconds, and having one can save hours of debugging.
Branching with snapshots
Section titled “Branching with snapshots”Git lets you branch code so an experiment cannot damage the main line. Snapshots bring the same workflow to the whole container.
Main state (snapshot: "production-stable") │ ├──→ Experiment A: try new database schema │ Result: works. Create snapshot "with-new-schema" │ ├──→ Experiment B: try different AI model │ Result: failed. Restore to "production-stable" │ └──→ Experiment C: try new auth system Result: promising. Create snapshot "auth-v2-wip"Everything happens on the same container, with nothing to clone, provision, or wait for. You are not creating new machines; you are recording states of one machine and moving between them.
# Save the current statehoody snapshots create -c $CONTAINER_ID --alias "main-branch"
# Experiment: try a risky database migrationhoody terminal sessions exec -c $CONTAINER_ID \ --command "python3 migrate.py --destructive"
# If it worked: save the resulthoody snapshots create -c $CONTAINER_ID --alias "after-migration"
# If it failed: restore and try something else (use the exact `name` returned by create/list)hoody snapshots restore -c $CONTAINER_ID --name "main-branch"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Save current stateawait client.api.containers.createSnapshot(id, { alias: 'main-branch'});
// Try experiment...
// Worked? Save the resultawait client.api.containers.createSnapshot(id, { alias: 'experiment-success'});
// Failed? Restore (use the exact `name` returned by create/list)await client.api.containers.restoreSnapshot(id, 'main-branch');# Create a branch pointcurl -X POST "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "main-branch"}'
# Experiment...
# Branch back to main (use the exact `name` returned by create/list)curl -X PUT "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots/main-branch" \ -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
Saves the current state as a branch point, then returns to it if an experiment fails. As with any restore, use the exact snapshot name from create or list, not necessarily the alias you set.
# Save current 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/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"main-branch"}&response=transparent
# Branch back to main
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/snapshots/main-branch&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.
Deployment rollbacks
Section titled “Deployment rollbacks”Snapshots turn deployments into reversible operations. Capture the state before you deploy, verify the result, and restore if verification fails.
1. Snapshot: POST /api/v1/containers/{prod}/snapshots {"alias": "pre-deploy-v2.1.0", "expiry": 30}
2. Deploy: Execute your deployment scripts
3. Verify: Health checks, smoke tests, monitoring
4. Success: Delete the snapshot after 30 days (or let it expire)
5. Failure: PUT /api/v1/containers/{prod}/snapshots/pre-deploy-v210 (the exact `name` from the step 1 response; the alias is sanitized to `[a-zA-Z0-9_-]`, so the dots are stripped) Production restored to the pre-deploy stateA restore does not mean rolling back the code, re-running migrations, and hoping the data is consistent. It rolls back everything at once, in one API call: code, config, data, installed packages, the entire disk state.
# Before deploymenthoody snapshots create -c $PROD_CONTAINER \ --alias "pre-deploy-v2.1.0" \ --expiry 30
# Deployhoody terminal sessions exec -c $PROD_CONTAINER \ --command "./deploy.sh v2.1.0"
# Verifyhoody terminal sessions exec -c $PROD_CONTAINER \ --command "curl -s localhost:3000/health | jq .status"
# If failed: roll back (use the exact `name` from the create output;# the alias is sanitized, so "pre-deploy-v2.1.0" lands as "pre-deploy-v210")hoody snapshots restore -c $PROD_CONTAINER --name "pre-deploy-v210"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
async function deployWithRollback(containerId: string, version: string) { // 1. Pre-deploy snapshot const snap = await client.api.containers.createSnapshot(containerId, { alias: `pre-deploy-${version}`, expiry: 30 });
// 2. Deploy (wait defaults to true; pass wait: false to poll for the result instead) const box = await client.withContainer(containerId); const exec = await box.terminal.execution.execute({ command: `./deploy.sh ${version}`, wait: false }); await box.terminal.execution.getResult(exec.data.command_id as string);
// 3. Health check const health = await fetch( `https://${PROJECT_ID}-${containerId}-http-3000.${SERVER_NAME}.containers.hoody.com/health` );
if (!health.ok) { // 4. Rollback await client.api.containers.restoreSnapshot( containerId, snap.data.snapshot.name ); throw new Error(`Deploy ${version} failed, rolled back`); }}# Complete deployment with rollback safety
# 1. Pre-deploy snapshotSNAP=$(curl -s -X POST "https://api.hoody.com/api/v1/containers/$PROD/snapshots" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "pre-deploy-v2.1.0", "expiry": 30}' \ | jq -r '.data.snapshot.name')
# 2. Deploycurl -X POST "https://$PROJECT-$PROD-terminal-1.$SERVER.containers.hoody.com/api/v1/terminal/execute" \ -H "Content-Type: application/json" \ -d '{"command": "./deploy.sh v2.1.0", "wait": true}'
# 3. Health checkSTATUS=$(curl -s -o /dev/null -w "%{http_code}" \ "https://$PROJECT-$PROD-http-3000.$SERVER.containers.hoody.com/health")
# 4. Rollback if failedif [ "$STATUS" != "200" ]; then curl -X PUT "https://api.hoody.com/api/v1/containers/$PROD/snapshots/$SNAP" \ -H "Authorization: Bearer $HOODY_TOKEN" echo "Rolled back to $SNAP"fiOne 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
These three links do not chain themselves — each is a standalone request, and you carry the result from one into the next by hand. Click the first to snapshot the container before deploying, then run the deploy link. If the health check after it fails, take the name field from the snapshot response (not the alias — pre-deploy-v2.1.0 sanitizes to something like pre-deploy-v210) and paste it in place of SNAPSHOT_NAME in the rollback link before using 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/CONTAINER_ID/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"pre-deploy-v2.1.0","expiry":30}&response=transparent
# Deploy
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-terminal-1.SERVER.containers.hoody.com/api/v1/terminal/execute&method=POST&json={"command":"./deploy.sh%20v2.1.0","wait":true}&response=transparent
# Rollback if the health check fails
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/snapshots/SNAPSHOT_NAME&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.
Historical debugging
Section titled “Historical debugging”Something broke, but you are not sure when. With snapshots at regular intervals, you can binary-search for the change:
Monday snapshot: workingTuesday snapshot: workingWednesday snapshot: BROKENRestore Tuesday’s snapshot and confirm it still works: the bug was introduced between Tuesday and Wednesday. If you keep hourly snapshots, narrow the window to an hour, then compare the two states to find the exact change.
The bug might be in a config file, an environment variable, a system package update, or a cron job that ran at 3 AM. Everything on disk is in the snapshot, so the search covers the whole machine rather than only the source tree.
Snapshot management
Section titled “Snapshot management”Create a snapshot
Section titled “Create a snapshot”# Create with aliashoody snapshots create -c $CONTAINER_ID --alias "milestone-v1"
# Create with expiration (auto-delete after 7 days)hoody snapshots create -c $CONTAINER_ID \ --alias "temp-experiment" \ --expiry 7
# Create permanent snapshot (no expiration)hoody snapshots create -c $CONTAINER_ID \ --alias "golden-image"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Create a snapshotconst snapshot = await client.api.containers.createSnapshot(containerId, { alias: 'milestone-v1', expiry: 90 // Days until auto-deletion});
console.log(snapshot.data.snapshot.name);// "milestone-v1"# 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": "milestone-v1", "expiry": 90 }'
# Response:# {# "data": {# "snapshot": {# "name": "milestone-v1",# "alias": "milestone-v1",# "created_at": "2026-03-04T14:30:45.000Z",# "stateful": false,# "size": 4589764321# }# }# }One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Creates a snapshot named milestone-v1 that expires automatically after 90 days. The response’s name field, not the alias, is what later restore or delete calls need.
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/snapshots&method=POST&bearer_token=TOKEN&json={"alias":"milestone-v1","expiry":90}&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.
List snapshots
Section titled “List snapshots”# List all snapshots for a containerhoody snapshots list -c $CONTAINER_IDimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const snapshots = await client.api.containers.listSnapshots(containerId);
for (const snap of snapshots.data.snapshots) { console.log(`${snap.alias || snap.name} - ${snap.created_at} - ${snap.stateful ? 'stateful' : 'stateless'}`);}curl "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots" \ -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
Lists every snapshot recorded for the container, including each one’s alias, name, and creation time.
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/snapshots&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.
Restore a snapshot
Section titled “Restore a snapshot”# Restore from a snapshot (use the exact `name` returned by create/list)hoody snapshots restore -c $CONTAINER_ID --name "milestone-v1"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Restore container to a previous stateawait client.api.containers.restoreSnapshot(containerId, snapshotName);// Container is now in the exact state it was when the snapshot was taken# Restore to snapshotcurl -X PUT "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots/milestone-v1" \ -H "Authorization: Bearer $HOODY_TOKEN"
# Container reverts to snapshot stateOne 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
Restores the container to the milestone-v1 snapshot. This is destructive — everything changed since that snapshot is lost, so snapshot the current state first if you want to keep it.
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/snapshots/milestone-v1&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.
Delete a snapshot
Section titled “Delete a snapshot”# Delete a snapshot to free storagehoody snapshots delete -c $CONTAINER_ID --name "milestone-v1"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
await client.api.containers.deleteSnapshot(containerId, snapshotName);// Storage freed immediatelycurl -X DELETE "https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots/milestone-v1" \ -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
Deletes the milestone-v1 snapshot and frees its storage immediately.
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/snapshots/milestone-v1&method=DELETE&bearer_token=TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Snapshot strategies
Section titled “Snapshot strategies”Snapshots before AI tasks
Section titled “Snapshots before AI tasks”Snapshot before every AI task and set the expiry to 7 days. If the AI’s changes survive a week of use, the snapshot deletes itself. If something surfaces before then, you have the week to catch it.
# Alias pattern: before-ai-{task}-{date}POST /api/v1/containers/{id}/snapshots{"alias": "before-ai-auth-rewrite-2026-03-04", "expiry": 7}Deployment milestones
Section titled “Deployment milestones”Snapshot before and after every deployment. Keep the “before” for 30 days (rollback window). Keep the “after” permanently if the version is a major release.
# Before deploy: temporary{"alias": "pre-deploy-v2.1.0", "expiry": 30}
# After deploy (major version): permanent{"alias": "v2.0.0-stable"}Daily snapshots
Section titled “Daily snapshots”Use cron or hoody-cron to snapshot every container daily. Set expiry to 30 days. You always have a month of daily restore points, and old snapshots clean themselves up.
Template images
Section titled “Template images”Set up a development environment once and snapshot it permanently. When a new team member joins, copy the container from that snapshot. One golden image produces as many copies as you need.
# The golden image: never expires{"alias": "dev-template-2026-q1"}
# New team member:POST /api/v1/containers/{template}/copy{"target_project_id": "...", "name": "alice-dev", "source_snapshot": "dev-template-2026-q1"}Snapshots in incident response
Section titled “Snapshots in incident response”When a container is compromised:
- Snapshot the compromised state for forensic analysis
- Restore the last known-good snapshot so production runs from a clean state
- Compare the two snapshots to identify exactly what changed: which files were modified, which processes were added, what data was exfiltrated
- Delete the compromised snapshot after analysis
You lose neither the evidence nor the uptime: the attacker’s changes stay in a snapshot for later study while production keeps serving from a clean state. The whole sequence is a handful of API calls.
What Git cannot do
Section titled “What Git cannot do”Git versions code. Snapshots version everything else.
| Git | Snapshots | |
|---|---|---|
| Source code | Yes | Yes |
| Database state | No | Yes |
| System configuration | Partially (dotfiles) | Yes (all of /etc) |
| Installed packages | No (requires rebuild) | Yes (exact binary state) |
| Environment files | No (.env in .gitignore) | Yes |
| On-disk app/browser data | No | Yes |
| Network configuration | No | Yes |
| Restore | Rebuild (clone + install + build + migrate) | One API call |
Git versions what you wrote and snapshots version what you run; together they cover the entire stack.
Next: Realms & Projects explains how containers are organized.