Skip to content

Create copies of containers across projects and servers. Synchronize copies with source changes. One template, infinite instantiations.

After mastering snapshots for time travel, you need container duplication for redundancy, team environments, and disaster recovery.


Official Technical Reference:

This Foundation page explains copy & sync concepts and workflows. For complete endpoint documentation:

Copy Operations:

Sync Operations:

Related:


Container copy creates a complete, independent duplicate:

Source Container (Production)
↓ (copy operation)
New Container (Staging)

The copy includes:

  • ✅ Entire filesystem (all files, directories)
  • ✅ Configuration (environment vars, resource allocation)
  • ✅ Installed software (packages, dependencies)
  • ✅ Data (databases, user files)
  • ✅ Snapshots (all previous snapshots copied too)

The copy gets:

  • 🆕 New container ID (independent lifecycle)
  • 🆕 New SSH key (security requirement)
  • 🆕 New service URLs (different project/container IDs)
  • 🔗 Reference to source (via source_container_id)

Copy runs independently. Changes to copy don’t affect source. Changes to source don’t affect copy (unless you sync).


Use Case 1: Create Staging from Production

Section titled “Use Case 1: Create Staging from Production”

Get exact production state for testing:

POST Copy production to staging
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

Now staging is identical to production - same code, same data, same configuration. Test updates there before deploying to prod.

Every developer gets identical setup:

POST Copy template for new team member
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

One perfect template → infinite developer environments. No “works on my machine” issues.

Redundancy across geographic regions:

POST Copy to different geographic region
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

If US server fails: EU backup can go live immediately.

Duplicate for parallel testing:

Terminal window
# Copy to test different approaches
POST /api/v1/containers/{app_id}/copy
{"target_project_id": "{project}", "name": "variant-a"}
POST /api/v1/containers/{app_id}/copy
{"target_project_id": "{project}", "name": "variant-b"}

Run experiments in parallel without affecting original.


POST Copy a container
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

Response:

{
"statusCode": 201,
"message": "Container copy initiated successfully",
"data": {
"id": "01bcdef123456789abcdef012",
"name": "container-copy",
"status": "copying",
"source_container_id": "890abcdef12345678901cdef",
"project_id": "67e89abc123def456789abcd",
"server_id": "63f8b0e5c9a1b2d3e4f5a6b7",
"server_name": "node-us",
"created_at": "2025-11-09T15:00:00.000Z"
}
}

Copy process runs asynchronously. Status progresses: copyingrunning (the copy starts automatically once the background job finishes).

Copy-on-Write (CoW) Technology:

  • Speed: Only unique or changed data blocks transferred
  • 💾 Storage: Shared blocks referenced, not duplicated
  • 🔄 Efficiency: Incremental changes minimize network transfer
  • 📦 Scalability: Create dozens of copies without proportional storage cost

Typical copy time: see the Copy Timing breakdown by container size below — under 10 GB, same-server copies usually finish in 1–2 minutes; cross-server times scale with network bandwidth. Sync operations are typically faster, at 10 seconds to 3 minutes for an incremental update.

Duplicate to different project:

POST Copy to different project
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

Use cases:

  • Client demo environments
  • Separate staging/production projects
  • Team member personal projects
  • Experiment isolation

Duplicate to different geographic location:

POST Copy to different server
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

Benefits:

  • Geographic redundancy
  • Lower latency for EU users
  • Disaster recovery (different datacenter)

Slower copy time due to network transfer between servers.

Use a known-good snapshot as source:

POST Copy from specific snapshot
/api/v1/containers/{container_id}/copy
Click "Run" to execute the request

Why specify snapshot:

  • Source container may have changed since you last tested
  • Copy from proven stable state
  • Reproducible environments (always copy from v1.0.0 snapshot)

If omitted: Copies source’s current state (latest snapshot if running, or current filesystem).


Copies MUST use different SSH keys than source.

Terminal window
# Omit ssh_public_key → auto-generated
POST /api/v1/containers/{source_id}/copy
{
"target_project_id": "{project}",
"name": "copy-with-auto-key"
}

Hoody generates new key pair automatically. Copy is secured with unique credentials.

Terminal window
# Provide your own public key
POST /api/v1/containers/{source_id}/copy
{
"target_project_id": "{project}",
"name": "copy-with-custom-key",
"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB..."
}

Best practice: Different team members → different SSH keys.


Update a copy to match source container’s current state.

Sync performs incremental update from source to copy:

Source Container (updated with new features)
↓ (sync operation)
Copy Container (receives updates incrementally)

Sync transfers:

  • ✅ Filesystem changes (new/modified files)
  • ✅ Deleted files (removed from copy)
  • ✅ Updated data (databases, caches)

Sync preserves:

  • ✅ Copy’s unique settings (name, SSH key, color)
  • ✅ Copy’s container ID
  • ✅ Copy’s service URLs
  • ✅ Copy’s network/firewall configuration

Much faster than full copy (only changes transferred).

POST Sync copy with source container
/api/v1/containers/{container_id}/sync
Click "Run" to execute the request

Response:

{
"statusCode": 200,
"message": "Container sync initiated successfully",
"data": {
"container_id": "01bcdef123456789abcdef012",
"source_container_id": "890abcdef12345678901cdef",
"status": "copying"
}
}

Sync runs asynchronously. Typical time: 10 seconds to 3 minutes depending on data volume changed.

Sync only works if:

  1. Container was created via copy operation (has source_container_id)
  2. Source container still exists
  3. You have access to source container

If source deleted: Cannot sync (orphaned copy).


Copy (Full Duplication)

When: First time creating duplicate

Process:

  • Creates complete independent container
  • Full data transfer
  • New container ID and URLs
  • Can be in different project/server

Time:

  • Same server: 1-5 minutes
  • Cross-server: 5-15 minutes

Storage:

  • Full container size

Use for:

  • Initial environment setup
  • Geographic replication
  • Team onboarding

Sync (Incremental Update)

When: Updating existing copy

Process:

  • Transfers only changes
  • Preserves copy’s unique settings
  • Same container ID and URLs
  • Requires existing copy

Time:

  • Typically 10 seconds to 3 minutes, longer for multi-GB diffs
  • Only changed data transferred

Storage:

  • Incremental (only changes)

Use for:

  • Keeping staging updated
  • Propagating bug fixes
  • Syncing team environments

Workflow: Copy once (full) → Sync many times (incremental).


Scenario 1: Staging Synced with Production

Section titled “Scenario 1: Staging Synced with Production”
Terminal window
# Day 1: Create staging from production
hoody containers copy $PROD_ID \
--target-project-id $STAGING_PROJECT \
--name "staging-api"
# Week 1: Sync staging to get production fixes
hoody containers sync $STAGING_COPY_ID
# Week 2: Sync again (incremental, fast)
hoody containers sync $STAGING_COPY_ID

Keep staging current without full re-copy.

Terminal window
# New team member Alice joins - copy template
hoody containers copy $TEMPLATE_ID \
--target-project-id $ALICE_PROJECT \
--name "alice-dev-env"
# Template gets updated - sync Alice's environment
hoody containers sync $ALICE_COPY_ID
# Another developer Bob joins
hoody containers copy $TEMPLATE_ID \
--target-project-id $BOB_PROJECT \
--name "bob-dev-env"

One template keeps entire team synchronized.

Terminal window
# Copy production to EU for redundancy
hoody containers copy $PROD_US_ID \
--target-project-id $PROJECT_ID \
--target-server-id $EU_SERVER_ID \
--name "prod-eu-replica"
# Monthly: Sync EU replica with US changes
hoody containers sync $PROD_EU_ID

If US datacenter fails: Switch to EU replica via proxy alias.

Terminal window
# Copy production to test new feature
curl -X POST "https://api.hoody.icu/api/v1/containers/{prod_id}/copy" \
-H "Authorization: Bearer $HOODY_TOKEN" \
-d '{
"target_project_id": "{same_project}",
"name": "feature-auth-v2"
}'
# Work on feature in the copy
# (Terminal, display, files all available)
# Feature complete? Snapshot it
POST /api/v1/containers/{feature_copy_id}/snapshots
{"alias": "feature-auth-v2-complete"}
# Copy this feature container to production
POST /api/v1/containers/{feature_copy_id}/copy
{
"target_project_id": "{production_project}",
"name": "prod-with-auth-v2"
}

Safe experimentation with production data.


1. Copy initiated (status: copying)
2. Source snapshot created (if needed)
3. Snapshot transferred to target server
4. New container provisioned from snapshot
5. SSH keys generated/configured
6. Copy complete and started (status: running)

Track progress:

Terminal window
# Check copy status
curl "https://api.hoody.icu/api/v1/containers/{new_copy_id}" \
-H "Authorization: Bearer $HOODY_TOKEN"
# Watch for: "status": "copying" → "status": "running"

Required:

  • target_project_id - Destination project

Optional:

  • target_server_id - Destination server (defaults to source server)
  • name - Copy name (auto-generated if omitted)
  • ssh_public_key - Custom SSH key (auto-generated if omitted)
  • source_snapshot - Specific snapshot to copy from (uses latest if omitted)
  • copy_firewall_rules - Copy the source’s firewall rules (ACL) to the copy. Default: false
  • copy_network_rules - Copy the source’s network rules/settings to the copy. Default: false

Same server:

  • 10 GB container: ~1-2 minutes
  • 50 GB container: ~3-5 minutes
  • 100 GB container: ~5-10 minutes

Cross-server (data transfer over network):

  • 10 GB container: ~3-5 minutes
  • 50 GB container: ~10-15 minutes
  • 100 GB container: ~20-30 minutes

Depends on:

  • Container size
  • Network bandwidth between servers
  • Server load
  • Snapshot size

Keep copies updated with source changes.

Sync when source has updates:

  • Code deployments to production → sync staging
  • Template improvements → sync team environments
  • Security patches → sync all replicas
  • Data updates → sync backups

How often:

  • Development: Daily or on-demand
  • Staging from production: After each prod deployment
  • Disaster recovery: Weekly or monthly
  • Team environments: When template updates
POST Sync copy with source
/api/v1/containers/{container_id}/sync
Click "Run" to execute the request

What happens:

  1. Source’s latest snapshot is captured
  2. Changed files identified (incremental diff)
  3. Changes transferred to copy
  4. Copy’s filesystem updated
  5. Copy restarted (if was running)

Sync is incremental: Only changes transferred, much faster than full re-copy.

Terminal window
# Update existing copy
POST /api/v1/containers/{copy_id}/sync

Advantages:

  • ✅ Faster (incremental)
  • ✅ Preserves copy’s unique settings
  • ✅ Same container ID/URLs
  • ✅ Less bandwidth usage
  • ✅ Copy relationship preserved (source_container_id)

When: Source has incremental updates

Prefer sync for regular updates. Re-copy only when starting fresh.


// Automated sync script (run via cron at 2 AM)
async function syncStaging() {
const token = process.env.HOODY_TOKEN;
const stagingCopyId = process.env.STAGING_CONTAINER_ID;
console.log('Starting nightly staging sync...');
// Sync staging with production
const response = await fetch(
`https://api.hoody.icu/api/v1/containers/${stagingCopyId}/sync`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
}
);
const result = await response.json();
if (response.ok) {
console.log('Staging synced successfully');
console.log(`Sync status: ${result.data.status}`);
} else {
console.error('Sync failed:', result.message);
// Send alert via hoody-notifications
}
}

Run daily: Staging stays current with production automatically.

// Sync all developer environments with template updates
async function syncTeamEnvironments(templateId, teamCopyIds) {
const token = process.env.HOODY_TOKEN;
const headers = { 'Authorization': `Bearer ${token}` };
// Sync all copies in parallel
const syncs = await Promise.all(
teamCopyIds.map(copyId =>
fetch(`https://api.hoody.icu/api/v1/containers/${copyId}/sync`, {
method: 'POST',
headers
}).then(r => r.json())
)
);
console.log(`Synced ${syncs.length} team environments`);
// Notify team of updates via Slack/email
}
// Run after template updates
syncTeamEnvironments(
'template_container_id',
['alice_copy_id', 'bob_copy_id', 'charlie_copy_id']
);

Keep entire team synchronized with latest tools/configuration.

Pattern 3: Conditional Sync Based on Source Changes

Section titled “Pattern 3: Conditional Sync Based on Source Changes”
// Only sync if source has recent updates
async function conditionalSync(sourceId, copyId) {
const token = process.env.HOODY_TOKEN;
const headers = { 'Authorization': `Bearer ${token}` };
// Get source container details
const source = await fetch(
`https://api.hoody.icu/api/v1/containers/${sourceId}`,
{ headers }
).then(r => r.json());
// Get copy details
const copy = await fetch(
`https://api.hoody.icu/api/v1/containers/${copyId}`,
{ headers }
).then(r => r.json());
// Compare update times
const sourceUpdated = new Date(source.data.updated_at);
const copyUpdated = new Date(copy.data.updated_at);
// Sync only if source is newer
if (sourceUpdated > copyUpdated) {
console.log('Source has updates, syncing...');
await fetch(
`https://api.hoody.icu/api/v1/containers/${copyId}/sync`,
{ method: 'POST', headers }
);
} else {
console.log('Copy is current, no sync needed');
}
}

Avoid unnecessary syncs when source hasn’t changed.


Check if container is a copy:

GET Check container copy status
/api/v1/containers/{container_id}
Click "Run" to execute the request

Response includes:

{
"data": {
"id": "01bcdef123456789abcdef012",
"source_container_id": "890abcdef12345678901cdef",
...
}
}

If source_container_id is not null → container is a copy.

GET List all containers to find copies
/api/v1/containers
Click "Run" to execute the request

Parse the response for containers where source_container_id matches your source container ID.

Then you can sync all copies programmatically.

Copy and sync operations do not return separate history IDs. Lineage is tracked through the container’s source_container_id field — the only copy-relationship field exposed by the API.

{
"id": "01bcdef123456789abcdef012",
"source_container_id": "890abcdef12345678901cdef"
}

Use for:

  • Tracking the relationship between a copy and its source
  • Discovering all copies of a source container (filter on source_container_id)
  • Determining sync eligibility (only containers with a source_container_id can sync)

Pattern 1: Production -> Staging -> Development

Section titled “Pattern 1: Production -> Staging -> Development”
Terminal window
# Weekly: Copy production to staging
hoody containers copy $PROD_ID \
--target-project-id $STAGING_PROJECT --name "staging-app"
# Daily: Sync staging with production changes
hoody containers sync $STAGING_ID
# Developers: Copy staging to personal envs
hoody containers copy $STAGING_ID \
--target-project-id $DEV_PROJECT --name "alice-dev"
# As needed: Sync dev envs with staging
hoody containers sync $ALICE_DEV_ID

Cascading environment updates.

Terminal window
# Primary in US
container: prod-us
# Copy to EU
POST /api/v1/containers/{prod_us_id}/copy
{
"target_project_id": "{project}",
"target_server_id": "{eu_server}",
"name": "prod-eu"
}
# Copy to Asia
POST /api/v1/containers/{prod_us_id}/copy
{
"target_project_id": "{project}",
"target_server_id": "{asia_server}",
"name": "prod-asia"
}
# After US deployment, sync all regions
POST /api/v1/containers/{prod_eu_id}/sync
POST /api/v1/containers/{prod_asia_id}/sync

Global deployment via copy & sync.

Terminal window
# 1. Snapshot tested container
hoody snapshots create --container $TEST_ID --alias "ready-for-prod"
# 2. Copy to production from that snapshot
hoody containers copy $TEST_ID \
--target-project-id $PROD_PROJECT \
--name "prod-v2" \
--source-snapshot "snap-20251109-143045"
# 3. Re-point alias to new container (delete + recreate)
hoody proxy delete $ALIAS_ID
hoody proxy create --container-id $NEW_PROD_ID \
--alias "prod" --program "web" --index 1 --target-path "/"

Reproducible deployments from known-good snapshots.


Terminal window
# ✅ Correct - generate new key for copy
POST /api/v1/containers/{source_id}/copy
{
"target_project_id": "{project}",
"name": "copy",
# Omit ssh_public_key → auto-generated unique key
}
# Or provide different key
POST /api/v1/containers/{source_id}/copy
{
"target_project_id": "{project}",
"name": "copy",
"ssh_public_key": "ssh-rsa DIFFERENT_KEY..."
}

Never reuse SSH keys across containers.

Terminal window
# Before syncing with major changes:
# 1. Snapshot copy's current state
curl -X POST "https://api.hoody.icu/api/v1/containers/{copy_id}/snapshots" \
-H "Authorization: Bearer $HOODY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"alias": "before-sync"}'
# 2. Perform sync
curl -X POST "https://api.hoody.icu/api/v1/containers/{copy_id}/sync" \
-H "Authorization: Bearer $HOODY_TOKEN"
# 3. If sync breaks something, restore
curl -X PATCH "https://api.hoody.icu/api/v1/containers/{copy_id}/snapshots/before-sync" \
-H "Authorization: Bearer $HOODY_TOKEN"
containers-mapping.yml
production:
container_id: 890abcdef12345678901cdef
server: node-us
copies:
- name: staging-api
container_id: 01bcdef123456789abcdef012
project: staging-project
sync_schedule: daily
- name: prod-eu-replica
container_id: 12cdef123456789abcdef0123
project: production-project
server: node-eu
sync_schedule: weekly

Track which containers are copies and sync schedules.

Terminal window
# Instead of copying current state (might be broken):
# 1. Identify stable snapshot
GET /api/v1/containers/{source_id}/snapshots
# Find: "production-stable-v1.0.0"
# 2. Copy from that snapshot
POST /api/v1/containers/{source_id}/copy
{
"target_project_id": "{project}",
"name": "guaranteed-stable",
"source_snapshot": "snap-20251109-143045"
}

Reproducible environments from known-good states.

Terminal window
# Check source still exists
curl "https://api.hoody.icu/api/v1/containers/{source_id}" \
-H "Authorization: Bearer $HOODY_TOKEN"
# If 404: Cannot sync (orphaned copy)
# Need to delete and re-copy from different source

Can I copy a container to a different user’s account?

Section titled “Can I copy a container to a different user’s account?”

No. Copies must be within your own projects. To share containers with others, use storage shares or export/import workflows.

What happens if source container is deleted?

Section titled “What happens if source container is deleted?”

The copy continues running independently (it’s a separate container). However, you can no longer sync—the relationship is broken. Consider the copy orphaned.

Yes! Sync as often as needed. Each sync is incremental, transferring only changes since last sync. Common pattern: Daily syncs from production to staging.

Yes. All source snapshots are copied too. The copy gets complete snapshot history from the source, enabling time travel in the duplicate.

Can I copy to a different project and server simultaneously?

Section titled “Can I copy to a different project and server simultaneously?”

Yes:

Terminal window
POST /api/v1/containers/{source}/copy
{
"target_project_id": "{different_project}",
"target_server_id": "{different_server}",
"name": "cross-everything-copy"
}

Both parameters can be different from source.

No. Proxy aliases are separate configuration, not part of container state. After copying, create new aliases for the copy or update existing aliases to point to it.

No. Sync is unidirectional: source → copy. To propagate changes from copy to source, manually transfer data or make the copy the new source (stop using old source).

What if copy has local changes when I sync?

Section titled “What if copy has local changes when I sync?”

Local changes in copy are overwritten by source state. Sync replaces copy’s filesystem with source’s filesystem. If copy has important changes, snapshot before sync, or propagate changes to source first.

Copy: Same as creating new container (storage, compute). Sync: Minimal (bandwidth for changes only). Both use source snapshot as transfer mechanism.


Problem: Copy returns error or stays in “copying” state

Solutions:

  1. Check source container exists and is accessible:

    Terminal window
    GET /api/v1/containers/{source_id}
    # Should return 200, not 404
  2. Verify target project exists:

    Terminal window
    GET /api/v1/projects/{target_project_id}
  3. Check target server has capacity:

    Terminal window
    GET /api/v1/servers/{target_server_id}
    # Verify enough resources
  4. If cross-server, check network connectivity:

    • Network issues between servers can stall copy
    • Contact support if persistent

Problem: Sync operation returns 400 Bad Request

Cause: Container is not a copy, or source no longer exists

Solutions:

  1. Verify container is a copy:

    Terminal window
    GET /api/v1/containers/{container_id}
    # Check: source_container_id is not null
  2. Verify source still exists:

    Terminal window
    GET /api/v1/containers/{source_container_id}
    # Should return 200
  3. If source deleted:

    • Copy is orphaned
    • Cannot sync anymore
    • Option A: Use copy as new source
    • Option B: Create new copy from different source

Problem: Copy taking very long

Typical times:

  • Same server, 50 GB: ~3-5 minutes
  • Cross-server, 50 GB: ~10-15 minutes

If much slower:

  1. Check container size:

    Terminal window
    GET /api/v1/containers/{source_id}
    # Check: container filesystem usage
    # Larger containers = longer copy time
  2. Cross-server copies are slower:

    • Network transfer adds significant time
    • 100+ GB containers can take 30+ minutes
  3. Server load:

    • High server load slows operations
    • Try during off-peak hours

Problem: Sync completes but copy still has old data

Possible causes:

  1. Source hasn’t changed:

    Terminal window
    GET /api/v1/containers/{source_id}
    # Check updated_at timestamp
    # If old, source hasn't been modified
  2. Sync transferred but copy not restarted:

    Terminal window
    # Restart copy to apply changes
    POST /api/v1/containers/{copy_id}/restart
  3. Changes in copy override sync:

    • If copy has local modifications, check carefully
    • Sync should overwrite but verify data is updated

Master container duplication:

  1. Snapshots → - Source for copy operations
  2. Images → - Template containers from images
  3. Create, Edit, Delete → - Container fundamentals

Use copies with:

Understanding gained:

  • ✅ Copy creates complete independent duplicate
  • ✅ Sync updates copy with source changes
  • ✅ Copies can be cross-project and cross-server
  • ✅ SSH keys must be unique per container
  • ✅ Sync is incremental (faster than re-copy)
  • ✅ source_container_id tracks copy relationship
  • ✅ Copies survive source deletion (orphaned)

One perfect container.
Copy to staging, dev, backup.
Sync to propagate updates.
Independent lifecycle, shared lineage.

Copy and synchronize containers directly from your browser:

Copy a container - omit target_server_id to use same server as source

Path Parameters

Authentication

🔒 Not Authenticated

Use the authentication widget in the header to login or set an API token

Authentication: Authorization: Bearer header (auto-attached for trusted domains)

Custom Headers

Request Body (JSON)

Sync a copied container with its source - only works for containers created via copy

Path Parameters

Authentication

🔒 Not Authenticated

Use the authentication widget in the header to login or set an API token

Authentication: Authorization: Bearer header (auto-attached for trusted domains)

Custom Headers

Request Body (JSON)