Skip to content

The old way: three months of Terraform, Kubernetes manifests, CI/CD pipelines, reverse proxy configs, SSL certificates, and a prayer that staging matches production.

The Hoody way: two containers, a handful of HTTP calls, and a URL you can share before lunch.

We are going to build a full-stack application from scratch — a React frontend, a TypeScript API backend, a SQLite database with session management, and a production domain. Everything runs inside containers where every service is already an HTTP endpoint. No Docker. No Nginx. No deploy scripts. Just URLs composing with URLs.


Here is what we are building:

your-app.com (Proxy Alias)
|
┌────────────┴────────────┐
v v
┌──────────────┐ ┌──────────────┐
│ Frontend │ │ Backend │
│ Container │ HTTP │ Container │
│ │ ──────> │ │
│ hoody-daemon│ │ hoody-exec │
│ (React app) │ │ (API routes)│
│ │ │ hoody-sqlite│
│ hoody-code │ │ (database) │
│ (VS Code) │ │ │
└──────────────┘ └──────────────┘

Two containers. One project. Every arrow is an HTTP call. Every box is a URL.


First, create a project to organize your full-stack app.

Terminal window
# Create the project
hoody projects create --alias "my-saas-app"
# Create the backend container
hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "backend" \
--container-image "debian-12" \
--hoody-kit
# Create the frontend container
hoody containers create --project $PROJECT_ID \
--server-id $SERVER_ID \
--name "frontend" \
--container-image "debian-12" \
--hoody-kit

You now have two containers. Each has the full Hoody Kit HTTP stack running. Total time: seconds.


The backend lives entirely inside hoody-exec. No Express. No Fastify. No server configuration. You write functions in files and they become HTTP endpoints.

Use hoody-sqlite to set up your data layer:

Terminal window
# Create the users table
hoody db exec-transaction --db /hoody/databases/app.db \
--transaction '[{"statement": "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"}]'
# Create the posts table
hoody db exec-transaction --db /hoody/databases/app.db \
--transaction '[{"statement": "CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER REFERENCES users(id), title TEXT NOT NULL, body TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"}]'

Use the KV store built into hoody-sqlite for session management — no Redis, no Memcached, no third service:

Terminal window
# Store a session token
hoody kv set "session:abc123" \
--db /hoody/databases/app.db \
--body '{"user_id": 1, "expires": "2026-04-01T00:00:00Z"}'

Create your API endpoint scripts. Each file becomes a URL automatically:

Terminal window
# Write the users endpoint
hoody exec scripts write \
--path "api/users.ts" \
--content "// @mode serverless\n// @cors reflective\n// @timeout 5000\n\nconst SQLITE_URL = \"https://'$PROJECT_ID'-'$BACKEND_ID'-sqlite-1.'$SERVER'.containers.hoody.icu\";\n\nif (metadata.method === \"GET\") {\n const result = await fetch(SQLITE_URL + \"/api/v1/sqlite/db?db=/hoody/databases/app.db\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ transaction: [{ query: \"SELECT * FROM users ORDER BY created_at DESC\" }] })\n });\n return await result.json();\n}\n\nif (metadata.method === \"POST\") {\n const { email, name } = req.body;\n const result = await fetch(SQLITE_URL + \"/api/v1/sqlite/db?db=/hoody/databases/app.db\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ transaction: [{ query: \"INSERT INTO users (email, name) VALUES (?, ?)\", values: [email, name] }] })\n });\n return await result.json();\n}" \
--create-dirs

That script is now live at:

https://$PROJECT_ID-$BACKEND_ID-exec-1.$SERVER.containers.hoody.icu/api/users

No deployment. No build step. No restart. The file IS the API.


Use hoody-terminal to scaffold a React app inside the frontend container:

Terminal window
# Install Node.js and scaffold React app.
# --ephemeral auto-generates an isolated session; without it --terminal-id is required.
hoody terminal sessions exec --ephemeral \
--command "curl -fsSL https://bun.sh/install | bash && \
bun create vite my-app --template react-ts && \
cd my-app && bun install"

Configure the React app to call the backend API. The backend is just a URL — no environment variable gymnastics, no proxy configuration:

// src/api.ts -- inside your React app
const API_BASE = 'https://PROJECT_ID-BACKEND_ID-exec-1.SERVER.containers.hoody.icu';
export async function getUsers() {
const res = await fetch(`${API_BASE}/api/users`);
return res.json();
}
export async function createUser(email: string, name: string) {
const res = await fetch(`${API_BASE}/api/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, name }),
});
return res.json();
}

Use hoody-daemon to run the dev server as a managed background process:

Terminal window
# Start the React dev server as a daemon
hoody daemon programs create \
--name "react-dev" \
--command "cd /root/my-app && bun run dev --host 0.0.0.0 --port 3000" \
--user root

Your React app is now running. View it live in hoody-display or access it through the container URL.


Transform the cryptographic container URL into a clean production domain using proxy aliases.

Terminal window
# Create alias for the frontend
hoody proxy create \
--container-id $FRONTEND_ID \
--program daemon --index 1 \
--alias "my-saas-app"
# Create alias for the API
hoody proxy create \
--container-id $BACKEND_ID \
--program exec --index 1 \
--alias "api-my-saas-app"

Now your app is live at my-saas-app.hoody.icu with the API at api-my-saas-app.hoody.icu. You can also connect your own domain — see Connect Your Domain.


Never deploy without a safety net. Snapshot both containers before you announce to the world:

Terminal window
# Snapshot backend
hoody snapshots create -c $BACKEND_ID \
--alias "pre-launch-backend"
# Snapshot frontend
hoody snapshots create -c $FRONTEND_ID \
--alias "pre-launch-frontend"

Something breaks post-launch? Restore both containers in seconds. The entire application — code, database, configuration — rolls back to the exact moment you snapshotted.


Here is your full-stack application as a service composition:

┌──────────────────────────────────────────────────────────┐
│ HOODY PROJECT │
│ "my-saas-app" │
│ │
│ ┌────────────────────┐ ┌─────────────────────────┐ │
│ │ FRONTEND CONTAINER│ │ BACKEND CONTAINER │ │
│ │ │ │ │ │
│ │ terminal-1 │ │ exec-1 ← API routes │ │
│ │ (dev tools) │ │ (users.ts, posts.ts) │ │
│ │ │ │ │ │
│ │ daemon-1 │ │ sqlite-1 ← database │ │
│ │ (React dev server)│ │ (users, posts, KV) │ │
│ │ │ │ │ │
│ │ code-1 │ │ terminal-1 │ │
│ │ (VS Code in │ │ (maintenance) │ │
│ │ browser) │ │ │ │
│ │ │ │ daemon-1 │ │
│ │ display-1 │ │ (background jobs) │ │
│ │ (live preview) │ │ │ │
│ └────────┬───────────┘ └──────────┬──────────────┘ │
│ │ │ │
│ │ HTTP calls │ │
│ └─────────────>─────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PROXY ALIASES │ │
│ │ my-saas-app.hoody.icu → frontend:daemon-1 │ │
│ │ api-my-saas-app.hoody.icu → backend:exec-1 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SNAPSHOTS │ │
│ │ pre-launch-backend (entire backend state) │ │
│ │ pre-launch-frontend (entire frontend state) │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

Every box is a URL. Every arrow is an HTTP request. Every component is snapshotable, shareable, and composable.


With the app running, your daily development looks like this:

  1. Open hoody-code (VS Code in browser) to edit frontend or backend files
  2. Open hoody-terminal side by side for running tests, checking logs
  3. Open hoody-display for live preview of the React app
  4. Query hoody-sqlite directly via its web UI to inspect data
  5. Snapshot before any risky change — restore in seconds if something breaks
  6. Share the URL with your team — they are instantly in your development environment

No local setup. No “works on my machine.” No environment drift. The development environment IS the production environment, separated only by a proxy alias.


When your app grows, Hoody grows with it:

  • Add more containers for microservices — each is just another URL
  • Use SQLite Drive to share databases across containers via /hoody/databases/
  • Use Shared Storage for cross-container files via /hoody/shared/
  • Add hoody-cron for scheduled jobs (backups, cleanups, reports)
  • Add hoody-browser for automated testing of your frontend
  • Point hoody-agent at your codebase and let AI handle pull requests

The architecture is the same at 1 container or 100. HTTP in, HTTP out, URLs all the way down.