A job orchestration framework powered by BullMQ, Postgres, and Hono. Define jobs in a config file, trigger them via CLI, HTTP API, or cron, and monitor everything through a built-in dashboard.
- Language-agnostic -- run Python, Bash, Node, or any command. If it runs in a shell, cinnamon can orchestrate it.
- Multi-tenant -- teams and API keys isolate workloads. Each job run is scoped to the team that triggered it. The dashboard is team-scoped for regular users; super admins see all.
- Durable -- every run is logged to Postgres with status, stdout, stderr, timing, and structured results.
- Observable -- query job history, inspect runs, check schedules, and stream live logs through the REST API or dashboard.
- Notifiable -- get Slack, Discord, or generic webhook notifications on job success or failure.
flowchart LR
CLI["CLI"] --> Queue["BullMQ (Redis)"]
API["API server (Hono)"] -->|"POST /v1/jobs/:name/trigger"| Queue
Dashboard["Dashboard (React)"] -->|"/api/dashboard/*"| API
Queue --> Worker[worker]
Worker --> Handler[job handler]
Worker --> JobsLog["cinnamon.jobs_log (Postgres)"]
Requires Bun and Docker Compose.
The fastest way to start is bun create cinnamon:
bun create cinnamon my-app
cd my-appThe scaffolding wizard sets up a working Cinnamon instance with Postgres, Redis, the dashboard, and an example job. Start infrastructure, then run:
docker compose up -d postgres redis
bun run db:migrate
bun run seed:team # creates a team + API key (save the cin_... key)Open two terminals:
bun run worker # terminal 1
bun run dev # terminal 2Open http://localhost:5173/dashboard to view the dashboard.
cinnamon init # paste your API key when prompted
cinnamon trigger hello-world # trigger a job
cinnamon status hello-world # check the resultIf you want to contribute or work on the framework itself:
git clone https://github.com/ianuriegas/cinnamon.git
cd cinnamon
bun install
cp .env.example .env
docker compose up -d postgres redis
bun run db:migrate
bun run seed:teamThen start the worker and dev server as above.
Three steps: config, script, trigger.
1. Define the job in cinnamon.config.ts:
export default defineConfig({
jobs: {
"my-job": {
command: "python3",
script: "./jobs/my-job/my-script.py",
timeout: "30s",
description: "My custom job",
},
},
});Any command that can run in a shell works -- python3, bash, bun, node, curl, etc.
2. Create the script at the path you specified:
# jobs/my-job/my-script.py
import json
result = {"processed": 42, "status": "ok"}
print(json.dumps(result)) # last line of JSON stdout → stored in jobs_log.result3. Trigger it:
cinnamon trigger my-job # via CLI
# or
curl -X POST http://localhost:3000/v1/jobs/my-job/trigger \
-H "Authorization: Bearer cin_<your_key>"Add a schedule field (cron syntax) to run it automatically:
"my-job": {
command: "python3",
script: "./jobs/my-job/my-script.py",
timeout: "30s",
schedule: "0 * * * *", // every hour
},See Jobs and config and Writing scripts for the full spec.
Jobs can send webhooks on success or failure. Cinnamon auto-detects Discord and Slack URLs and formats messages accordingly; any other URL receives a generic JSON payload.
"my-job": {
command: "python3",
script: "./jobs/my-job/my-script.py",
timeout: "30s",
notifications: {
on_failure: [{ url: "${DISCORD_WEBHOOK_URL}" }],
on_success: [{ url: "${SLACK_WEBHOOK_URL}" }],
},
},${VAR} references are resolved from environment variables at runtime.
Cinnamon is designed to be added as a git submodule inside your project. This keeps your jobs and config in your repo while pulling in the framework.
git submodule add https://github.com/ianuriegas/cinnamon.git cinnamonUse Docker Compose's merge feature to layer your app on top of cinnamon's base services:
docker compose -f cinnamon/docker-compose.yml -f docker-compose.override.yml up -dYour override file adds project-specific config (env vars, volumes, extra services) while inheriting Postgres, Redis, worker, scheduler, and API from cinnamon.
See examples/deploy/docker/ for a working override example.
Cinnamon tables live in a dedicated cinnamon Postgres schema, so they never collide with your app's tables. Run cinnamon's migrations separately from your own:
cd cinnamon && bun run cinnamon:migrate && cd ..See Migrations for the full dual-migration setup.
The dashboard is open by default for local dev. To require Google sign-in:
- Place your GCP OAuth
client_secret.jsonin the project root (or setGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETin.env). - Generate a session secret and add it to
.env:
bun run generate:secretPaste the output as SESSION_SECRET in .env.
-
For local dev with
bun run dev(Vite on port 5173), create.env.localwithBASE_URL=http://localhost:5173so the OAuth callback and session cookie use the same origin. KeepBASE_URL=http://localhost:3000in.envfor Docker/production..env.localoverrides.envwhen running locally and is gitignored. -
Set super-admin emails (these users get full dashboard access on first login):
SUPER_ADMINS=you@gmail.com,teammate@gmail.com
- Optionally enable access requests so non-admin users can request dashboard access:
ACCESS_REQUESTS_ENABLED=true
When SESSION_SECRET is unset, auth is disabled and the dashboard remains open.
See Access requests for the full operator guide and .env.example for all options.
- API reference -- endpoints, query params, and curl examples
- Jobs and config -- job definitions,
cinnamon.config.ts - Writing scripts -- output contract for shell scripts
- Migrations -- schema namespacing, dual migration pattern
- Project structure -- directory layout, scripts, CLI
- Access requests -- self-service access request flow for the dashboard
- Deployment -- Docker Compose and CI/CD overview
- Resilience -- zombie cleanup and worker self-healing
- Postgres -- health checks, SQL shell, queries
- Redis -- health checks, debugging
- Tests -- test coverage and details
- Examples -- reference implementations (Spotify integration, deploy configs)