Skip to content
Hoody.com

Every cloud provider makes the same promise: “Your data is secure.” Then they read your data to train AI models, hand it to governments without telling you, or suffer breaches that expose millions of records. The promise is structurally impossible to keep because their architecture requires them to have access to your data in order to serve it.

Hoody’s architecture is different. Not because we are more trustworthy — because the architecture leaves far less to take on trust: your data lives on a machine rented to you and is served from that machine directly, with no central Hoody proxy tier in the path.

You rent bare metal servers. Physical machines. Not virtual instances on shared hardware. Not containers on someone else’s hypervisor. Actual hardware where you control the disk, the memory, and the network. Your containers run on that machine, and your data never transits a Hoody datacenter — the proxy that serves your URLs runs on your own server, not on ours.

This is not a privacy feature. It is the architecture.


In Hoody, this means your data does not live in a Hoody datacenter and does not pass through a Hoody proxy tier. It sits on a machine rented to you, served by software running on that same machine. Here is how that changes the trust picture:

LayerTraditional CloudHoody
HardwareShared with other tenantsDedicated bare metal you control
HypervisorProvider-controlledNone — containers run on your hardware
Disk encryptionProvider holds the keysLUKS on every host, always on — the key is never stored on the machine it unlocks, so seized hardware yields ciphertext
NetworkProvider can inspect trafficTLS terminates on your own rented server — no central proxy tier in the path
BackupsProvider can read snapshotsSnapshots live on your disk
AI trainingYour data may be usedNot used for training; paid model calls reach the upstream provider

The fundamental difference: in traditional cloud your data sits in the provider’s datacenter. In Hoody it sits on a machine rented to you, and Hoody administers that machine rather than hosting your data. Your server is a physical machine. Your containers are processes on that machine. Your data is bytes on that disk. Hoody manages the orchestration layer — container creation, proxy routing, service coordination — but your actual data stays on hardware you control.


hoody-files supports encrypted storage through the crypt backend, which wraps another storage backend and encrypts file contents and filenames before they are written to it — decrypting transparently on read. The passphrase lives in your container’s backend configuration, sealed in a manifest encrypted with ChaCha20-Poly1305, and is never returned by the API.

Terminal window
# Configure encrypted storage backend (the crypt layer). The new backend has
# its own File ID -- capture it, every later call selects the backend by that id.
BACKEND_ID=$(hoody files backends connect crypt -c $CONTAINER_ID \
--remote "<connected-backend-id>:secure-data" \
--password "$ENCRYPTION_KEY" -o json | jq -r '.data.id')
# Write a file to encrypted storage. The contents come from the request body:
# pipe them in, or point --input at a local file.
hoody files put secrets/api-keys.json -c $CONTAINER_ID \
--backend "$BACKEND_ID" \
--input ./api-keys.json
# Read it back -- transparently decrypted
hoody files get secrets/api-keys.json -c $CONTAINER_ID \
--backend "$BACKEND_ID"

What happens on disk: The file secrets/api-keys.json is stored as encrypted bytes. If someone physically extracts the disk, they get ciphertext. If someone reads the underlying storage out-of-band, they get ciphertext. But anyone who can reach the running hoody-files service reads plaintext — the service holds the key — so proxy permissions remain the control that matters for live access. The file is only readable through the hoody-files service with the correct key.


For secrets that need to be available to applications without storing them in plaintext files, use hoody-sqlite’s KV store as a secrets vault:

Terminal window
# Store secrets in the KV store (--db is required; --create-db-if-missing on first use)
hoody kv set "vault:stripe_key" --db /hoody/databases/app.db --create-db-if-missing \
--body '"sk_live_abc123..."'
hoody kv set "vault:database_url" --db /hoody/databases/app.db \
--body '"postgres://user:pass@host:5432/db"'
hoody kv set "vault:jwt_secret" --db /hoody/databases/app.db \
--body '"your-256-bit-secret"'
# Retrieve secrets programmatically
hoody kv get "vault:stripe_key" --db /hoody/databases/app.db

The KV store lives in SQLite, which lives on your bare metal disk. Secrets rest on your rented machine and are served from it directly — no central Hoody tier in the path. SQLite itself is not encrypted: the LUKS volume under it covers the machine being stolen, and mounting that database on a crypt-wrapped backend adds the layer that still holds while the host is running.


Container Isolation as the Security Boundary

Section titled “Container Isolation as the Security Boundary”

Each container is a complete, isolated Linux environment. The isolation is not just logical — it is enforced at the operating system level:

┌──────────────────────────────────────────────────┐
│ YOUR BARE METAL SERVER │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Container A │ │ Container B │ │ Cont. C │ │
│ │ │ │ │ │ │ │
│ │ Own files │ │ Own files │ │ Own files│ │
│ │ Own network │ │ Own network │ │ Own net │ │
│ │ Own procs │ │ Own procs │ │ Own procs│ │
│ │ Own users │ │ Own users │ │ Own users│ │
│ │ │ │ │ │ │ │
│ │ CANNOT SEE │ │ CANNOT SEE │ │ CANNOT │ │
│ │ B or C │ │ A or C │ │ SEE A, B │ │
│ └─────────────┘ └─────────────┘ └──────────┘ │
│ │
│ Shared: CPU, RAM, Disk (but isolated views) │
└──────────────────────────────────────────────────┘

What container isolation means in practice:

  • Container A cannot read Container B’s files, even though they share the same physical disk
  • A process in Container A cannot see or kill processes in Container B
  • Network traffic is isolated — containers cannot sniff each other’s traffic
  • A compromised container is contained to itself, and nothing it can reach gives it your other containers
  • An AI agent running in Container A has full root inside A — and exactly the network reach you leave it: egress starts open, and the firewall pattern below closes it

This is why container isolation is the right security primitive for the AI era. When you give an AI agent root access to a container, what it can touch is that one container — not your server, not your other projects. No isolation boundary is absolute; the point is that the unit of compromise is small, disposable, and restorable from a snapshot in seconds.


Pattern 1: Realm-Restricted Tokens for Tenant Isolation

Section titled “Pattern 1: Realm-Restricted Tokens for Tenant Isolation”

When building multi-tenant applications, use realms to create isolated API scopes:

Terminal window
# NOTE: Realms are not created. A realm is any 24-hex identifier you attach to
# resources via realm_ids -- it starts existing the moment a resource carries it.
# `hoody realms list` reports the realm IDs already in use across your resources:
hoody realms list
# Create containers within each realm by assigning the label at creation time
hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "acme-app" \
--realm-ids 507f1f77bcf86cd799439011 \
--hoody-kit
hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "globex-app" \
--realm-ids 507f1f77bcf86cd799439012 \
--hoody-kit

Realm-scoped API tokens can only access containers within their realm. A token restricted to the acme realm cannot see, list, or access any container in the globex realm. The isolation is enforced at the API level.

Layer encryption for sensitive data:

// Layer 1: hoody-files crypt backend encrypts the filesystem
// Layer 2: Application-level encryption for specific fields
// @mode serverless
const SQLITE = "https://PROJECT-CONTAINER-sqlite-1.SERVER.containers.hoody.icu";
// Store with application-level encryption
const crypto = require('crypto');
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const sensitiveData = JSON.stringify({ ssn: '123-45-6789', salary: 150000 });
let encrypted = cipher.update(sensitiveData, 'utf8', 'hex');
encrypted += cipher.final('hex');
await fetch(SQLITE + "/api/v1/sqlite/db?db=/hoody/databases/app.db", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
transaction: [{ query: "INSERT INTO employee_records (name, encrypted_data, iv) VALUES (?, ?, ?)", values: ["Alice Chen", encrypted, iv.toString('hex')] }]
})
});

Two layers: the filesystem is encrypted by the crypt backend, and sensitive fields are encrypted again at the application level. Even if someone bypasses the filesystem encryption, the data inside is still ciphertext.

Pattern 3: Control Network Egress with Firewall

Section titled “Pattern 3: Control Network Egress with Firewall”

Prevent containers from sending data to unauthorized destinations:

Terminal window
# Start from a clean firewall (reset detaches the ACL and returns the
# container to an open network state), then build an allow-list and a
# final deny rule so only approved destinations can be reached.
# `reset` is destructive, so it asks for confirmation -- pass -y to run unattended.
hoody firewall reset -c $CONTAINER_ID -y
# Resolve the hostnames you want to allow to IPv4/CIDR first — the firewall
# API only accepts numeric destinations (host `dig +short api.stripe.com`).
STRIPE_CIDR=$(dig +short api.stripe.com | awk '{print $1"/32"; exit}')
GITHUB_CIDR=$(dig +short api.github.com | awk '{print $1"/32"; exit}')
hoody firewall egress create -c $CONTAINER_ID \
--action allow --protocol tcp --destination-port 443 \
--destination "$STRIPE_CIDR" --description "Allow Stripe API"
hoody firewall egress create -c $CONTAINER_ID \
--action allow --protocol tcp --destination-port 443 \
--destination "$GITHUB_CIDR" --description "Allow GitHub API"
# Finally, drop everything else outbound so only the approved destinations
# above remain reachable. Add one rule per protocol to cover tcp, udp, and icmp4.
# tcp and udp rules must carry a destination port -- 1-65535 covers every one of them.
hoody firewall egress create -c $CONTAINER_ID \
--action drop --protocol tcp --destination "0.0.0.0/0" --destination-port 1-65535 \
--description "Deny all TCP egress"
hoody firewall egress create -c $CONTAINER_ID \
--action drop --protocol udp --destination "0.0.0.0/0" --destination-port 1-65535 \
--description "Deny all UDP egress"
hoody firewall egress create -c $CONTAINER_ID \
--action drop --protocol icmp4 --destination "0.0.0.0/0" --description "Deny all ICMP egress"

With an explicit allow-list followed by a deny-all egress rule, the container cannot phone home, cannot exfiltrate data, cannot communicate with any server you have not explicitly approved. This is essential when running untrusted code or AI agents that might attempt to send data externally.


Physical servers mean physical locations. This is the simplest compliance argument there is:

Data residency: Your server is in Frankfurt. Your data is in Frankfurt. Not “primarily” in Frankfurt. Not “replicated from” Frankfurt. Physically, magnetically, on a disk in Frankfurt.

GDPR: European personal data lives on European hardware. Your container data stays on that machine unless you send it elsewhere — no “our servers might be anywhere” hedging.

HIPAA: Protected health information on dedicated hardware with encrypted filesystems, firewall-controlled network access, and container isolation between patient datasets.

SOC 2: Audit trail through HTTP request logs. Access control through proxy permissions. Encryption through crypt backend. Isolation through containers. Every compliance requirement maps to an HTTP-observable, configurable control.

┌────────────────────────────────────────────────┐
│ YOUR SERVER: Frankfurt, Germany │
│ Physical address: DataCenter GmbH, Room 4B │
│ │
│ Container: patient-records │
│ ├── Encrypted filesystem (crypt backend) │
│ ├── Firewall: egress deny-all │
│ ├── Proxy: IP whitelist (clinic IPs only) │
│ ├── Realm: healthcare-prod │
│ └── Snapshots: daily, 90-day retention │
│ │
│ Data location: This building. This rack. │
│ Data access: Clinic IPs only. │
│ Data encryption: crypt backend, keys on-server.│
│ Data retention: 90-day snapshot history. │
│ Audit trail: Every HTTP request logged. │
└────────────────────────────────────────────────┘

Try expressing that compliance posture with a shared cloud VM. You cannot. The architecture prevents it.


Here is the argument that makes private workflows urgent rather than merely prudent:

AI agents running in your infrastructure can see everything. When you give an AI agent access to a container — terminal, files, database, browser — it has the same access as a developer. It can read credentials, query databases, browse the filesystem.

On shared infrastructure, a compromised or misbehaving AI agent could:

  • Exfiltrate data through network requests
  • Access other tenants’ resources through hypervisor vulnerabilities
  • Persist malicious code that survives container restarts
  • Send your data to the AI provider’s training pipeline

On Hoody’s bare metal + container architecture:

  • Container isolation keeps the agent inside its own container
  • Firewall rules prevent unauthorized network communication
  • Snapshots let you roll back any changes the agent made
  • Bare metal means no hypervisor attack surface
  • Full-disk encryption is always on, on every host, so a drive read outside the running machine yields ciphertext
  • Opt-in encrypted storage (crypt backend) adds the layer that survives a live host — those files stay ciphertext even to something reading the unlocked volume
Terminal window
# The AI safety workflow:
# 1. Create an isolated container for the experiment and capture its ID
EXPERIMENT_ID=$(hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "ai-experiment" \
--hoody-kit -o json | jq -r '.id')
# 2. Lock down network — reset to a clean baseline, then add a deny-all
# egress rule so the agent has no outbound path (allow-list specific
# destinations with `hoody firewall egress create` if it needs any).
# `reset` is destructive, so pass -y to skip its confirmation prompt.
# tcp and udp rules must carry a destination port -- 1-65535 covers every one.
hoody firewall reset -c $EXPERIMENT_ID -y
hoody firewall egress create -c $EXPERIMENT_ID \
--action drop --protocol tcp --destination "0.0.0.0/0" --destination-port 1-65535 \
--description "Deny all TCP"
hoody firewall egress create -c $EXPERIMENT_ID \
--action drop --protocol udp --destination "0.0.0.0/0" --destination-port 1-65535 \
--description "Deny all UDP"
hoody firewall egress create -c $EXPERIMENT_ID \
--action drop --protocol icmp4 --destination "0.0.0.0/0" --description "Deny all ICMP"
# 3. Snapshot clean state
hoody snapshots create --container $EXPERIMENT_ID \
--alias "clean-slate"
# 4. Let the AI agent run. hoody-agent is an in-container Kit service reached
# through the Hoody Proxy, not the management Hoody API. Open a session bound to the container,
# then dispatch a turn into it (auto-approve answers confirm gates so the
# turn runs unattended). For a session-less one-shot, use
# `hoody agent headless create-run` instead.
SESSION_ID=$(hoody agent sessions create --realm global \
-c $EXPERIMENT_ID -o json | jq -r '.id')
hoody agent sessions prompt-sync --id $SESSION_ID \
--policy auto_approve \
--text "Analyze this dataset and build a classification model"
# 5. Inspect results
# 6. Restore clean state if needed. The restore key is the auto-generated
# snap-<timestamp> name (from `hoody snapshots list`), not the alias.
hoody snapshots restore -c $EXPERIMENT_ID --name "snap-20251109-143045" -y

The AI agent has full autonomy inside an air-gapped container. It can read data, write code, run experiments — but it cannot exfiltrate anything because the firewall blocks all outbound traffic. When the experiment is done, inspect the results. If anything looks wrong, restore the clean snapshot. The data never left your hardware.


The strongest security posture layers multiple controls:

Layer 1: Bare Metal → No shared hardware, no hypervisor attack surface
Layer 2: Container → Process isolation, filesystem isolation
Layer 3: Firewall → Network egress control, default-allow; add rules to restrict
Layer 4: Proxy Permissions → Authentication before HTTP reaches the container
Layer 5: Encrypted FS → LUKS on every host by default; crypt backend adds a live-host layer
Layer 6: Application → Field-level encryption, input validation
Layer 7: Snapshots → Rollback capability, audit via diff
Layer 8: Realms → API-level tenant isolation

Each layer is independently configurable through HTTP, and each can be audited through HTTP. They are not fully independent of one another — containers on a host share a kernel — but they fail separately: compromising one does not hand an attacker the rest.

This is not defense in depth as a marketing term. It is eight distinct, HTTP-configurable controls between an attacker and your data, each one something you can inspect and change.