Containers
Section titled “Containers”A Docker container is a build artifact: lightweight, stateless, and disposable. You bake an image, ship it, run it, and throw it away. Docker containers are packaging, not computing.
A Hoody container is a computer: a full Debian 13 Linux machine with systemd, its own filesystem, its own network stack, and its own process tree. It boots, runs services, and keeps its state, and it can hold terminals, desktops, databases, and browsers. It is a machine you work in.
Hoody containers run on hardware you control: flat-rate bare metal rented from the marketplace, or a shared free-tier server to start. There is no managed cloud layer between you and the host.
What a container includes
Section titled “What a container includes”When you create a Hoody container, you get a complete Linux computer:
- Debian 13 (Trixie) with full package manager (
apt) - systemd init system (service management, not a single entrypoint process)
- Own filesystem (persistent, writable, full Linux FHS)
- Own network stack (own IP, own DNS, own routing table)
- Own process tree (own PID namespace, isolated processes)
- 18 built-in HTTP services (terminal, display, files, exec, sqlite, browser, agent, code, curl, notifications, daemons, cron, pipe, notes, watch, run, tunnel, proxy logs)
Each of those services is a URL, live from the moment the container starts, with no setup or deployment step.
# Create a container: a full Linux computerhoody containers create --project abc123def456789012345678 \ --server-id node-us-server-id \ --name "dev-environment" \ --hoody-kit \ --dev-kit
# Once it is running, all 18 HTTP services are liveimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Spawn a full Linux computerconst container = await client.api.containers.create('abc123def456789012345678', { server_id: 'node-us-server-id', name: 'dev-environment', hoody_kit: true, dev_kit: true});
// Immediately available:// container.data?.id -> 890abcdef12345678901cdef// Terminal, Display, Files, Exec, SQLite, Browser, Agent,// Code, cURL, Notifications, Daemons, Cron, Pipe, Notes,// Watch, Run, Tunnel, Proxy Logs: all at predictable URLs# A single POST creates the containercurl -X POST "https://api.hoody.com/api/v1/projects/abc123def456789012345678/containers" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "server_id": "node-us-server-id", "name": "dev-environment", "hoody_kit": true, "dev_kit": true }'
# Response includes container ID# Construct any service URL:# https://{projectId}-{containerId}-terminal-1.node-us.containers.hoody.comOne 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 the container with all 18 services live. It runs through another
container’s curl-1 service, so the first container in a project still
comes from one of the other tabs.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/projects/abc123def456789012345678/containers&method=POST&bearer_token=TOKEN&json={"server_id":"node-us-server-id","name":"dev-environment","hoody_kit":true,"dev_kit":true}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Containers vs Docker
Section titled “Containers vs Docker”Docker solved packaging; Hoody containers are full machines. The two solve different problems.
| Docker container | Hoody container | |
|---|---|---|
| What it is | A process running from a filesystem image | A full Linux computer |
| Init system | None (or an improvised PID 1) | systemd |
| Persistence | Ephemeral by default (volumes are add-ons) | Persistent by default |
| Network | Shared bridge / host network | Own network stack with own IP |
| Services | One process per container (convention) | 18 built-in HTTP services + anything you install |
| Access | docker exec from the host | A URL, reachable from any network |
| State | Destroy and rebuild | Snapshot, restore, branch |
| OS | Stripped-down layers | Full Debian 13 with apt |
| Purpose | Ship software | Run computers |
Docker answers how to package and deploy an app. Hoody answers how to give someone a computer reachable from anywhere.
Containers vs VMs
Section titled “Containers vs VMs”Virtual machines also give you full computers. They predate the web as an interface, though: the assumption is that you SSH in, configure by hand, and manage each machine individually.
Hoody containers take HTTP as the primary interface:
| Traditional VM | Hoody container | |
|---|---|---|
| Access | SSH, VNC, RDP (specialized clients) | HTTPS (browser or curl) |
| Boot time | Minutes | Seconds |
| Overhead | Full OS + hypervisor | Lightweight (LXC + namespaces) |
| Density | Low (a full OS per VM) | High (containers share what is identical) |
| Isolation | Hardware-level (hypervisor) | Kernel-level (namespaces + seccomp) |
| Built-in services | None; install everything yourself | 18 HTTP services out of the box |
| Embeddable | No | Yes; every service is an iframe-able URL |
| AI-accessible | Requires SSH adapter | Native; the interface is HTTP |
| Snapshots | Slow (full disk image) | Instant (copy-on-write) |
The result is full, dedicated machines without the hypervisor overhead, the minutes-long boot, or the density penalty.
The 18 built-in services
Section titled “The 18 built-in services”Every Kit-enabled container (the default) comes with the Hoody Kit: 18 services that expose Linux capabilities as HTTP endpoints. They are the container’s primary interface.
| Service | URL segment | What it does |
|---|---|---|
| Terminal | terminal-N | Shell sessions via HTTP + WebSocket |
| Display | display-N | Full desktop environments (Xfce, etc.) via browser |
| Files | files-N | Filesystem access (read, write, delete, list) |
| SQLite | sqlite-N | SQL databases queryable via HTTP |
| Exec | exec-N | Scripts that become HTTP endpoints automatically |
| Browser | browser-N | Chrome/Chromium automation via REST |
| Agent | agent-N | Hoody Agent, the built-in AI agent (chat, sessions, memory), in your browser |
| Code | code-N | VS Code instances in the browser |
| cURL | curl-N | Transform any REST call into a GET URL |
| Notifications | n-N | Push notifications via HTTP |
| Daemons | daemon-N | Background process management |
| Cron | cron-N | Scheduled task management |
| Pipe | pipe-N | Streaming data transfer between devices |
| Notes | notes-N | Collaborative notebooks with real-time sync |
| Watch | watch-N | File change notifications via HTTP + WebSocket |
| Run | run-N | Multi-source app resolver (Nix, pkgx, AppImage, Docker/OCI) |
| Tunnel | tunnel-N | TCP tunneling over HTTP (expose or pull services) |
| Proxy Logs | logs-N | Access logs and traffic inspection for Hoody Proxy |
Each service is a URL, and each URL is reachable from any network. The container is a Linux machine you operate over HTTP.
# Run a command in the containerhoody terminal sessions exec -c 890abcdef12345678901cdef \ --command "apt update && apt install -y nodejs"
# Read a filehoody files get /home/user/app.js -c 890abcdef12345678901cdef
# Query a databasehoody db exec-transaction -c 890abcdef12345678901cdef \ --db /data/app.db \ --transaction '[{"query":"SELECT * FROM users"}]'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER_NAME});
// Terminal: run a commandconst result = await containerClient.terminal.execution.execute({ command: 'node --version', wait: true});
// Files: read application codeconst code = await containerClient.files.get('/home/user/app.js');
// SQLite: query the database (via direct fetch to the container service URL)const base = `https://${PROJECT_ID}-${CONTAINER_ID}`;const node = `${SERVER_NAME}.containers.hoody.com`;const data = await fetch(`${base}-sqlite-1.${node}/api/v1/sqlite/db?db=app`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction: [{ query: 'SELECT count(*) FROM users' }] })});# Terminal: install softwarecurl -X POST "https://$PROJECT-$CONTAINER-terminal-1.$SERVER.containers.hoody.com/api/v1/terminal/execute" \ -H "Content-Type: application/json" \ -d '{"command": "apt install -y python3", "wait": true}'
# Files: download a log filecurl "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/var/log/syslog" \ -o syslog.txt
# Display: take a screenshot of the desktopcurl "https://$PROJECT-$CONTAINER-display-1.$SERVER.containers.hoody.com/api/v1/display/screenshot" \ -o desktop.png
# Browser: navigate to a pagecurl "https://$PROJECT-$CONTAINER-browser-1.$SERVER.containers.hoody.com/browse?browser_id=0&url=https%3A%2F%2Fexample.com"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
The log and screenshot calls above use curl’s -o flag to save the
response to a local file; that flag has no equivalent in a link, so
only the two requests below are linked directly.
# Install a package
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":"apt%20install%20-y%20python3","wait":true}&response=transparent
# Navigate the browser
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-browser-1.SERVER.containers.hoody.com/browse?browser_id=0%26url=https://example.com&method=GET&response=transparent Container isolation
Section titled “Container isolation”Each container is a private computer. Its files, processes, and network are its own, separate from every other container’s.
Own filesystem. Containers cannot see each other’s files, and there are no shared volumes by default. Each container has its own root filesystem, its own /home, its own /etc. You can explicitly share directories between containers if you choose, but isolation is the default.
Own network. Each container has its own IP address, its own routing table, its own DNS configuration. Containers do not share a network bridge. They communicate via HTTP URLs through the proxy, the same way any two computers on the internet communicate.
Own processes. PID namespaces give every container its own process tree. What runs in one container is invisible to the next.
Kernel-enforced. The separation comes from Linux namespaces, seccomp filters, and a hardened kernel, not from application code.
Multiple instances
Section titled “Multiple instances”A container can expose multiple parallel sessions for instance-aware services. For those services, the instance number in the URL (-1, -2, -3) selects the session:
https://{projectId}-{containerId}-terminal-1.node-us.containers.hoody.com # Developer's shellhttps://{projectId}-{containerId}-terminal-2.node-us.containers.hoody.com # Build processhttps://{projectId}-{containerId}-terminal-3.node-us.containers.hoody.com # AI agent's session
https://{projectId}-{containerId}-display-1.node-us.containers.hoody.com # Main desktophttps://{projectId}-{containerId}-display-2.node-us.containers.hoody.com # Secondary monitor
https://{projectId}-{containerId}-sqlite-1.node-us.containers.hoody.com # SQLite servicehttps://{projectId}-{containerId}-sqlite-2.node-us.containers.hoody.com # Same SQLite service; the instance segment does not select a database# Both reach the same files; pick the database with ?db=app or ?db=analyticsThe container is the same in every case. For instance-aware services, different URLs identify concurrent sessions for different users or agents; SQLite instead selects the database with the db query parameter.
Multiplayer access comes from the URL scheme itself rather than from a feature you enable.
Container cost
Section titled “Container cost”A traditional VM consumes fixed resources whether you use it or not: a 2-core, 4GB VM costs the same idle as it does under load.
Hoody containers share what is identical. BTRFS copy-on-write storage means containers cloned from the same base image share disk blocks, and the shared host kernel and page cache mean one hundred containers running the same base Debian image do not consume one hundred times the resources. You pay only for what differs.
In practice:
- Development containers cost nearly nothing when idle
- Test containers share base OS memory with production containers
- Staging environments stop being a meaningful cost
- Per-feature containers are practical
- Each AI agent can have its own container
Containers have no per-unit cost: once a server exists, you can create as many as its capacity allows. That removes the usual reason to ration environments.
# Spawn 10 containers for parallel testingfor i in $(seq 1 10); do hoody containers create --project $PROJECT_ID \ --server-id $SERVER_ID \ --name "test-runner-$i" \ --hoody-kitdone
# Each gets 18 HTTP services, each isolated# Shared memory pages keep resource usage lowimport { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
// Spin up containers for each team memberconst names = ['alice', 'bob', 'carol', 'dave'];
const containers = await Promise.all( names.map(name => client.api.containers.create(projectId, { server_id: serverId, name: `dev-${name}`, hoody_kit: true, dev_kit: true }) ));
// 4 full Linux computers, each with 18 services// Cloned from the same base image, so they share disk blocks (BTRFS CoW)# Create containers in parallel; there is no per-unit costfor name in frontend backend database worker; do curl -X POST "https://api.hoody.com/api/v1/projects/$PROJECT_ID/containers" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"server_id\": \"$SERVER_ID\", \"name\": \"$name\", \"hoody_kit\": true, \"dev_kit\": true }" &donewait
# 4 computers created in parallel# Each with terminal, display, files, exec, sqlite...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 one container per open; the loop above fires this same call four
times in parallel, once per name (frontend, backend, database, worker).
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/projects/PROJECT_ID/containers&method=POST&bearer_token=TOKEN&json={"server_id":"SERVER_ID","name":"NAME","hoody_kit":true,"dev_kit":true}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Container lifecycle
Section titled “Container lifecycle”A container is not disposable. It keeps its state and history across every stage of its life.
Create. One POST to the API brings the container up with all services running.
Run. The container operates as a full Linux machine. Install software, run servers, write code, train models; everything persists.
Snapshot. Capture the entire filesystem state instantly. Snapshots are copy-on-write, so they cost almost nothing.
Stop. The container halts but keeps its filesystem. Start it again, and everything is where you left it.
Restore. Revert to any snapshot in one API call.
Copy. Duplicate a container to another server or project. Cloning a development environment for a new team member is one API call.
Delete. Destroy the container, and its URLs stop resolving.
At every stage, the container’s identity is its set of URLs. They appear when you create the container and disappear when you delete it.
The container as the base unit
Section titled “The container as the base unit”In most infrastructure, the unit is ambiguous: a server, a VM, a pod, a process, or a function, depending on the layer you are looking at. In Hoody, the unit is the container:
- A service runs in a container.
- An AI gets access through the container’s URLs.
- A risky experiment goes in its own container, away from everything else.
- Rolling back means restoring a container snapshot.
- Sharing an environment means sharing a container URL.
Everything else is built on top of containers, not beside them. That leaves one abstraction and one URL pattern to learn.
Next: The Hoody Proxy explains how URLs reach containers.