Daemons
Section titled “Daemons”Hoody Daemons is a process manager you drive over HTTP. It supervises any executable: Node.js, Python, Go, Rust, compiled binaries, and shell scripts. There is no CLI to install and no language-specific tooling, and a REST API creates programs, starts and stops them, and reports their state.
Capabilities
Section titled “Capabilities”- Program management - Create, configure, and delete daemon programs
- Process control - Start, stop, enable, and disable programs
- Status monitoring - Read process state, uptime, and PID
- Auto-restart - Choose whether a program restarts always, never, or only after an unexpected exit
- Priority control - Set the startup order so dependencies come up first
- Logging - Point stdout and stderr at log files
- User isolation - Run each process as a specific system user
- Environment - Set custom environment variables per program
API Endpoints Summary
Section titled “API Endpoints Summary”All endpoints are relative to your Daemon Manager service URL:
https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.comProgram management:
GET /api/v1/daemon/programs- List all programsGET /api/v1/daemon/programs/{id}- Get program detailsPOST /api/v1/daemon/programs/add- Create new programPOST /api/v1/daemon/programs/edit/{id}- Update programPOST /api/v1/daemon/programs/remove/{id}- Delete programPOST /api/v1/daemon/programs/reset- Reset programs to defaults
Process control:
POST /api/v1/daemon/programs/{id}/enable- Enable programPOST /api/v1/daemon/programs/{id}/disable- Disable programPOST /api/v1/daemon/programs/{id}/start- Start processPOST /api/v1/daemon/programs/{id}/stop- Stop process
Monitoring:
GET /api/v1/daemon/health- Daemon service health probeGET /api/v1/daemon/status- All process statusesGET /api/v1/daemon/status/{id}- Single process status
Logs:
GET /api/v1/daemon/programs/{id}/logs- Get program stdout/stderr logsGET /api/v1/daemon/quick-start/{id}/logs- Get ephemeral program logs
Quick start (ephemeral programs):
GET /api/v1/daemon/quick-start- List ephemeral programsPOST /api/v1/daemon/quick-start- Launch ephemeral programGET /api/v1/daemon/quick-start/{id}/status- Get ephemeral program statusPOST /api/v1/daemon/quick-start/{id}/stop- Stop ephemeral program
Process lifecycle
Section titled “Process lifecycle”Enable → Start → Stop → Disable → Remove
# Create a daemon program (required fields are passed as flags)hoody daemon programs create --name web-server \ --command '/usr/bin/node /app/server.js' --user www-data -c my-container-id
# Enable and start (use the numeric Program ID returned by create/list)hoody daemon programs enable <program-id> -c my-container-idhoody daemon programs start <program-id> -c my-container-id
# Check statushoody daemon programs status <program-id> -c my-container-id
# Stop and removehoody daemon programs stop <program-id> -c my-container-idhoody daemon programs disable <program-id> -c my-container-idhoody daemon programs delete <program-id> -c my-container-id --yesimport { 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 });
// Create a daemon programconst result = await containerClient.daemon.programs.add({ name: 'web-server', command: '/usr/bin/node /app/server.js', user: 'www-data', autorestart: 'true', directory: '/app',});const programId = result.data!.id;
// Enable and startawait containerClient.daemon.control.enable(programId);await containerClient.daemon.control.start(programId);
// Check statusconst status = await containerClient.daemon.status.get(programId);
// Stop and removeawait containerClient.daemon.control.stop(programId);await containerClient.daemon.control.disable(programId);await containerClient.daemon.programs.remove(programId);# Create a daemon programcurl -X POST "https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.com/api/v1/daemon/programs/add" \ -H "Content-Type: application/json" \ -d '{ "name": "web-server", "command": "/usr/bin/node /app/server.js", "user": "www-data", "autorestart": "true", "directory": "/app" }'
# Enable and startcurl -X POST ".../api/v1/daemon/programs/1/enable"curl -X POST ".../api/v1/daemon/programs/1/start"
# Check statuscurl "https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.com/api/v1/daemon/status/PROGRAM_ID"
# Stop and removecurl -X POST ".../api/v1/daemon/programs/1/stop"curl -X POST ".../api/v1/daemon/programs/1/disable"curl -X POST ".../api/v1/daemon/programs/remove/1"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 the program, then reads the status of the id the create call returned. The enable, start, stop, disable, and remove calls in between hit the same daemon host — see the HTTP tab for their exact paths.
# Create
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.com/api/v1/daemon/programs/add&method=POST&json={"name":"web-server","command":"/usr/bin/node%20/app/server.js","user":"www-data","autorestart":"true","directory":"/app"}&response=transparent
# Check status
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.com/api/v1/daemon/status/PROGRAM_ID&method=GET&response=transparent Add a program disabled
Section titled “Add a program disabled”enabled defaults to true, so a program you add starts running the moment supervisord picks it up. Pass enabled: false to add it without starting it, check the configuration, then enable and start it deliberately:
hoody daemon programs create --name web-server \ --command '/usr/bin/node /app/server.js' --user www-data \ --no-enabled --autorestart true --directory /app -c my-container-idimport { 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 });
const result = await containerClient.daemon.programs.add({ name: 'web-server', command: '/usr/bin/node /app/server.js', user: 'www-data', enabled: false, autorestart: 'true', directory: '/app',});const programId = result.data!.id;curl -X POST "https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.com/api/v1/daemon/programs/add" \ -H "Content-Type: application/json" \ -d '{ "name": "web-server", "command": "/usr/bin/node /app/server.js", "user": "www-data", "enabled": false, "autorestart": "true", "directory": "/app" }'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
Adds the program with enabled: false, so it sits configured but not running until you enable and start it.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-daemon-1.SERVER.containers.hoody.com/api/v1/daemon/programs/add&method=POST&json={"name":"web-server","command":"/usr/bin/node%20/app/server.js","user":"www-data","enabled":false,"autorestart":"true","directory":"/app"}&response=transparent Then enable, start, and monitor it as shown in the lifecycle example above.
Enable vs start
Section titled “Enable vs start”Enable is a configuration change:
- Makes the program available to supervisord
- Does not start the process
- Required before starting
Start is a runtime action:
- Launches the process
- Only works if the program is enabled
- Creates a running process with a PID
Example:
# This sequence is required:POST /programs/{id}/enable # Configuration: "program can run"POST /programs/{id}/start # Runtime: "start the process"
# This fails:POST /programs/{id}/start # Error: program not enabledAuto-restart policies
Section titled “Auto-restart policies”Configure how programs behave on crashes:
{ "name": "critical-service", "command": "/usr/bin/service", "user": "service", "autorestart": "true" // Always restart on exit}Options:
"true"- Always restart (recommended for services)"false"- Never restart (one-time tasks)"unexpected"- Restart only on crashes (not clean exits)
Program configuration
Section titled “Program configuration”Full program example:
{ "name": "api-server", "description": "Main REST API server", "command": "/usr/bin/node /app/api/server.js", "user": "api", "enabled": true, "boot": true, "autorestart": "true", "directory": "/app/api", "priority": 10, "stdout_logfile": "/var/log/api/stdout.log", "stderr_logfile": "/var/log/api/stderr.log", "environment": { "NODE_ENV": "production", "PORT": "3000", "DB_HOST": "localhost" }}Process states
Section titled “Process states”Monitor process health through the status endpoint:
- RUNNING - Process running normally with PID
- STOPPED - Process not running (expected)
- STARTING - Currently launching (temporary)
- STOPPING - Gracefully shutting down (temporary)
- BACKOFF - Failed to start, retrying
- FATAL - Failed to start after retries, manual intervention needed
Status response:
{ "success": true, "status": { "id": 1, "status": "RUNNING", "pid": 12345, "uptime": "2:15:30" }}Startup priority
Section titled “Startup priority”Control boot order when multiple programs depend on each other:
[ { "name": "database", "priority": 1, // Starts first "boot": true }, { "name": "cache", "priority": 5, // Starts second "boot": true }, { "name": "web-server", "priority": 10, // Starts last "boot": true }]Lower priority number = starts earlier.
Hoody Kit services
Section titled “Hoody Kit services”Use Cases
Section titled “Use Cases”Web servers and APIs
Section titled “Web servers and APIs”Run Node.js, Python, or Go servers as daemons with auto-restart. Configure logging for debugging, set a startup priority if there are dependencies, and monitor them through the status endpoint.
Background workers
Section titled “Background workers”Queue processors (Bull, Sidekiq, Celery), scheduled task runners, data sync services, cleanup jobs.
Custom background services
Section titled “Custom background services”Run custom application services, workers, and scripts with a defined startup order and auto-restart policy. Do not use Hoody Daemon to manage package-installed system services such as PostgreSQL, MySQL, Redis, MongoDB, nginx, or apache. Use the system service manager (systemd, OpenRC) for those.
Monitoring agents
Section titled “Monitoring agents”Prometheus exporters, log shippers, health check agents, metrics collectors.
Development servers
Section titled “Development servers”Hot-reload dev servers, file watchers, test runners, development proxies.
Microservices
Section titled “Microservices”Independent service processes, inter-service communication, graceful shutdown coordination, centralized process management.
Best Practices
Section titled “Best Practices”Initial configuration
Section titled “Initial configuration”Always add programs with enabled: false. Verify the configuration before enabling, test manually before setting boot: true, and turn on auto-restart only after stability testing.
Configuration updates
Section titled “Configuration updates”Stop the process first, then disable the program so auto-restart does not bring it back. Edit the configuration, enable and start it again, and verify the new configuration works before cleaning up.
Logging strategy
Section titled “Logging strategy”Always configure stdout and stderr logs, use absolute paths for log files, rotate logs to manage disk space, and watch them for errors and warnings.
User permissions
Section titled “User permissions”Run each program as an appropriate system user, never as root unless it is required. Create a dedicated user per service type and give it only the privileges it needs.
Dependency management
Section titled “Dependency management”Use the priority field for startup ordering: low numbers (1-5) for core services, higher numbers (10-20) for dependent services. Test the boot sequence thoroughly.
Monitoring
Section titled “Monitoring”Poll the /status endpoint regularly, alert on BACKOFF or FATAL states, track uptime for reliability metrics, and run health checks through other services.
Useful Questions
Section titled “Useful Questions”Q: What’s the difference between enable and start? Enable is configuration (“program can run”), start is runtime action (“launch the process”). You must enable before starting.
Q: How do I make a program start on boot?
Set boot: true in the configuration. The program will auto-start when the daemon service initializes.
Q: Can I update a running program? You must stop and disable it first, then edit, then enable and start again. Changes don’t apply to running processes.
Q: What happens if a process crashes?
Depends on autorestart: "true" restarts immediately, "false" stays stopped, "unexpected" restarts only on crashes.
Q: How do I run multiple instances of the same program? Create separate program configurations with different names, unique ports or sockets in the command, different data directories, and distinct priority values.
Q: Can I see process output?
Configure stdout_logfile and stderr_logfile, then read the logs from the filesystem or through the Files service, which can tail them in real time with the Files API.
Q: How do I check if a program is running?
Call GET /status/{id} and look for status: "RUNNING" and a pid field.
Troubleshooting
Section titled “Troubleshooting”Program won’t start (BACKOFF/FATAL)
Section titled “Program won’t start (BACKOFF/FATAL)”Cause: Command fails, port already in use, missing dependencies, wrong user permissions. Solution: Check the stderr log file, verify the command works manually, make sure the port is free, confirm the user exists and has permissions, and run the command directly as that user to test.
Can’t update a running program
Section titled “Can’t update a running program”Cause: Changes don’t apply to running processes. Solution: Follow the sequence: stop, disable, edit, enable, start. Supervisord only reloads config on enable and disable.
Auto-restart not working
Section titled “Auto-restart not working”Cause: autorestart: "false" or program disabled.
Solution: Set autorestart: "true", make sure the program is enabled, check it is not in FATAL state, and review the supervisord logs.
Program stops immediately after start
Section titled “Program stops immediately after start”Cause: Command exits normally, missing keep-alive loop, process detaches. Solution: Verify the command stays running rather than finishing as a one-shot task, add a keep-alive loop if needed, and check the logs for errors on startup.
Priority not respected on boot
Section titled “Priority not respected on boot”Cause: Boot delay not set, priority too similar.
Solution: Add delay_seconds between priorities, keep priority numbers at least 5 apart, and check that all programs have boot: true.
Status shows wrong state
Section titled “Status shows wrong state”Cause: Status cache or supervisord mismatch.
Solution: Refresh the status endpoint, check supervisord directly, restart the daemon manager if the mismatch persists, and confirm with ps whether the program is actually running.