Skip to content

Custom Services

The Service entity wraps any command-line process. It works with any language or framework — if you can run it in a terminal, you can run it in Sigil.

import { Service } from "sigil";
env.add(
new Service("my-api", {
command: ["node", "server.js"],
cwd: "./backend",
})
);

Pass environment variables that get merged with the parent process environment:

env.add(
new Service("my-api", {
command: ["python", "-m", "uvicorn", "main:app", "--port", "8000"],
cwd: "./backend",
env: {
DATABASE_URL: db.connectionString,
REDIS_URL: "redis://localhost:6379",
NODE_ENV: "development",
},
})
);

Without a ready check, Sigil marks the service as “running” as soon as the process spawns. With a ready check, it polls an HTTP endpoint until it responds:

env.add(
new Service("my-api", {
command: ["node", "server.js"],
readyCheck: {
url: "http://localhost:3000/health",
interval: 1000, // poll every 1s (default)
timeout: 30000, // fail after 30s (default)
},
})
);

This is important when other services depend on this one being fully ready before they start.

Interfaces are metadata describing what the service exposes:

import { Service, APIInterface, BrowserInterface } from "sigil";
// An API service
env.add(
new Service("backend", config, [new APIInterface(8000)])
);
// A web frontend
env.add(
new Service("frontend", config, [new BrowserInterface(3000)])
);
import path from "path";
const root = import.meta.dir;
env.add(
new Service("api", {
command: [
path.join(root, "backend/.venv/bin/uvicorn"),
"main:app",
"--host", "0.0.0.0",
"--port", "8000",
],
cwd: path.join(root, "backend"),
})
);
env.add(
new Service("api", {
command: ["go", "run", "."],
cwd: "./backend",
env: { PORT: "8080" },
readyCheck: { url: "http://localhost:8080/healthz" },
})
);
env.add(
new Service("frontend", {
command: ["bun", "run", "dev"],
cwd: "./frontend",
readyCheck: { url: "http://localhost:5173" },
})
);

Service output is streamed to the terminal with a name prefix:

[my-api] Server listening on port 8000
[my-api] Connected to database
[frontend] VITE v5.0.0 ready in 200ms
  • Start: Process is spawned via Bun.spawn(). If a readyCheck is configured, Sigil polls until the endpoint responds with a 2xx status.
  • Stop: Process receives SIGKILL. The service status transitions to "stopped".