Understanding containers
Section titled “Understanding containers”A Hoody container is a full Linux computer that arrives online, running Debian 13 by default. It runs systemd and has its own filesystem, its own network, and 19 built-in HTTP services, and every process, file, and database inside it has a URL from the moment it exists. It is closer to a machine than to a Docker image: you can install Docker, Podman, LXC, or a virtual machine inside one and run your own containers on top.
The primary interface is HTTP rather than SSH or a deploy pipeline: every process running inside is already an HTTPS endpoint, served with HTTP/2, so you fetch a container instead of shelling into it. ssh hoody.com still opens the Hoody CLI and Agent from any terminal, and nothing stops you from deploying to a container the conventional way. The proxy issues and renews the certificates; you never create or renew one yourself.
Included services
Section titled “Included services”Every container created with Hoody Kit enabled (hoody_kit: true) includes the services below. They all answer on the same hostname shape, and only the service label changes between them:
https://{projectId}-{containerId}-{service}-{instance}.{server}.containers.hoody.com| Capability | Service label | What it does |
|---|---|---|
| Shell access | terminal-1 | Persistent PTY sessions over REST and WebSocket. Sessions survive disconnects, and several people can attach to the same one. |
| File system | files-1 | Read, write, move, search, upload, and extract archives on any absolute container path over HTTP. With journaling enabled, it keeps each file’s past revisions and diffs. |
| Database | sqlite-1 | SQLite over HTTP: multi-statement SQL transactions plus a JSON key-value store with TTL, compare-and-swap, change history, and rollback. Databases live under /hoody/databases. |
| Desktop display | display-1 | An X11 desktop streamed to a browser tab. The API takes screenshots, sends clicks and keystrokes, manages windows, and reads or writes the clipboard. |
| Browser automation | browser-1 | Long-lived Chromium or Firefox instances driven by Playwright: navigate, screenshot, render PDFs, run JavaScript, read cookies and console or network logs, or attach over CDP. |
| Script execution | exec-1 | Serves each .ts or .js file in the scripts directory as an HTTP endpoint. Saving the file updates the endpoint; there is no build or deploy step. |
| AI agent (Hoody Agent) | agent-1 | The coding agent running inside the container. Open it from a browser tab, over ssh hoody.com, or with hoody agent; a new client resumes an available session when it can, and the HTTP API can create sessions, send prompts, and stream turns. |
| VS Code | code-1 | The VS Code editor in a browser tab, working on the container’s filesystem, with terminals and a debugger. Extensions come from the Open VSX marketplace inside the editor, from an HTTP call, or from a directory in the container. |
| HTTP composition | curl-1 | Sends HTTP requests from the container, as one-off calls, background jobs, or scheduled runs, with cookie jars. It can also turn any POST, PUT, or DELETE request into a plain GET URL. |
| Background processes | daemon-1 | Starts and supervises long-running programs through supervisord, with auto-restart and stdout and stderr logs kept on disk. |
| Scheduled tasks | cron-1 | Adds, lists, and removes crontab entries for a system user. Managed entries can expire, and hand-written crontab lines stay untouched. |
| Desktop notifications | n-1 | Shows and records desktop toasts on a container display, sent with an HTTP call. A browser page on n-1 that you keep open, with notification permission granted, also shows them as browser notifications; nothing arrives once the page is closed. |
| Data streaming | pipe-1 | Streams bytes from one HTTP sender to up to 256 receivers on a shared path. Live broadcasts let viewers join during the stream; ?ws relays messages both ways between two peers. Browser pages send files, receive downloads and share a screen, camera or microphone. Nothing is written to disk or replayed. |
| Collaborative notebooks | notes-1 | Notebooks of sections, pages, and database records with rich-text documents, comments, reactions, version history, attachments, and live updates over WebSocket. |
| File watching | watch-1 | Watches paths with inotify and delivers create, modify, remove, and rename events over SSE, WebSocket, or paginated history, with resume after a disconnect. |
| App resolution | run-1 | Resolves an app name such as firefox to a ready shell command, searching Nix, pkgx, AppImage, OCI images, and manifests. It returns the command and does not launch it. |
| TCP tunneling | tunnel-1 | Publishes an HTTP or WebSocket server from your laptop on the container’s public URL, or brings a laptop TCP port into the container’s loopback. |
| Proxy access logs | logs-1 | Read-only query, statistics, and live SSE tail of every request the proxy handled for this container, filterable by status level, method, and service. |
| Outbound HTTP proxy | egress | An HTTP proxy that sends requests out through the container. Setting an upstream changes the exit IP without changing the client. |
Substituting a real project, container, and server gives the shell for that container. Both ids are 24 hexadecimal characters:
https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-1.node-us.containers.hoody.comAll 19 ship in every Kit-enabled container. They are reachable from any device with a browser, and can be driven from any terminal through the Hoody CLI (ssh hoody.com).
One more Kit service answers on the same hostname shape without being one of the 19. bot-1 is the chat front door: it puts the CLI’s own commands behind a chat app such as Telegram. It is left out of the count because it is optional: a container can run without it. See Hoody Bot, or Run your container from a chat app for the registration walkthrough.
Create a container
Section titled “Create a container”# Create a container in your projecthoody containers create --project $PROJECT_ID --name "backend" --server-id $SERVER_ID
# List your containershoody containers listimport { HoodyClient } from 'hoody-sdk';const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: process.env.HOODY_TOKEN });
const container = await client.api.containers.create(PROJECT_ID, { server_id: SERVER_ID, name: 'backend'});
// The new container is already onlineconsole.log(container.data.id);curl -X POST https://api.hoody.com/api/v1/projects/$PROJECT_ID/containers \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"server_id": "'"$SERVER_ID"'", "name": "backend"}'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 same call as a single GET URL. It runs through the curl-1 service of a
container you already have, so the first container comes from one of the other
tabs. Every container after that can come from a link.
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":"backend"}&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 URL structure
Section titled “The URL structure”Every service in every container has a predictable URL:
https://{projectId}-{containerId}-{serviceName}-{serviceId}.{node}.containers.hoody.comFor example:
https://PROJECT_ID-CONTAINER_ID-terminal-1.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-files-1.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-display-1.node-us.containers.hoody.comDifferences from Docker
Section titled “Differences from Docker”| Feature | Docker | Hoody Containers |
|---|---|---|
| Base system | Minimal layers | Debian 13 with systemd by default; any marketplace image |
| Networking | Internal bridge, port mapping | Every service has a public URL |
| Access method | docker exec / SSH | HTTP from anywhere |
| Built-in services | None; you bring everything | 19 HTTP services included |
| Collaboration | Not designed for it | Multiplayer by default |
| Snapshots | Volume snapshots only | Full filesystem snapshots and restore |
| Multiple instances | Separate containers | terminal-1, terminal-2… in the same container |
Docker containers are build artifacts; Hoody containers are computers.
Multiple instances
Section titled “Multiple instances”A container can run several instances of the same service. Three terminals, two SQLite URLs, and a browser:
https://PROJECT_ID-CONTAINER_ID-terminal-1.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-terminal-2.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-terminal-3.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-sqlite-1.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-sqlite-2.node-us.containers.hoody.comhttps://PROJECT_ID-CONTAINER_ID-browser-1.node-us.containers.hoody.comAll six run in the same container. Each terminal instance has its own URL, its own process, and its own state. The two SQLite URLs reach the same SQLite service; use ?db=app and ?db=scratch to select different databases.
Container lifecycle
Section titled “Container lifecycle”# Start a stopped containerhoody containers start $CONTAINER_ID
# Stop a running containerhoody containers stop $CONTAINER_ID
# Snapshot before making changeshoody snapshots create --container $CONTAINER_ID --alias "before-experiment"
# Restore if something breaks (the CLI asks you to confirm; add -y to skip the prompt)hoody snapshots restore --container $CONTAINER_ID --name $SNAPSHOT_NAME// Startawait client.api.containers.start(CONTAINER_ID);
// Stopawait client.api.containers.stop(CONTAINER_ID);
// Snapshotconst snapshot = await client.api.snapshots.create(CONTAINER_ID, { alias: 'before-experiment'});
// Restoreawait client.api.snapshots.restore(CONTAINER_ID, SNAPSHOT_NAME);# Startcurl -X POST https://api.hoody.com/api/v1/containers/$CONTAINER_ID/start \ -H "Authorization: Bearer $TOKEN"
# Stopcurl -X POST https://api.hoody.com/api/v1/containers/$CONTAINER_ID/stop \ -H "Authorization: Bearer $TOKEN"
# Snapshotcurl -X POST https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"alias": "before-experiment"}'
# Restorecurl -X PUT https://api.hoody.com/api/v1/containers/$CONTAINER_ID/snapshots/$SNAPSHOT_NAME \ -H "Authorization: Bearer $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
Each action as a single GET URL. Route them through a different running
container’s curl-1 (OTHER_CONTAINER_ID below): a stopped container cannot
serve the request that starts it.
# Start
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/start&method=POST&bearer_token=TOKEN&response=transparent
# Stop
https://PROJECT_ID-OTHER_CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://api.hoody.com/api/v1/containers/CONTAINER_ID/stop&method=POST&bearer_token=TOKEN&response=transparent
# Snapshot
https://PROJECT_ID-OTHER_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-experiment"}&response=transparent
# Restore
https://PROJECT_ID-OTHER_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.
Container cost
Section titled “Container cost”Pricing is per server, not per container: you pay for the bare metal, and the containers on it carry no per-unit cost. Once a server exists, you can create as many as its capacity allows.
Under per-VPS pricing, three environments at $40 a month each cost $120 a month. On Hoody, one server carries all of them. That removes the usual reason to ration environments: a dev container, a staging copy, an experiment, and an AI playground can each get their own container.
Next: The Hoody Proxy →