A lightweight, observable job runner with a built-in web API
Project description
Jobrail
A lightweight job runner with persistent history, a built-in API, and Prometheus metrics. One container, one SQLite file, zero infrastructure dependencies.
Ofelia with memory. Sidekiq without Redis. Kubernetes CronJobs without Kubernetes.
Documentation
Full documentation is available at paracosm-lab.github.io/jobrail and in-app at localhost:8010/docs.
The problem
Every production system accumulates scheduled work: cert checks, backup verification, health sweeps, cleanup tasks, cost reports. This work isn't your application — it's the operational plumbing that keeps everything else running.
Your options today:
- Ofelia / cron — fire and forget. No history, no API, no metrics. When a job fails silently at 3 AM, you find out from your users.
- Celery / Sidekiq — built for distributed task queues, not a weekly cert check. You're standing up Redis, workers, and a monitoring layer for something that should be simple.
- Kubernetes CronJobs — requires Kubernetes. State lives in pod logs. "Did my backup job actually succeed last Tuesday?" is a kubectl archaeology project.
- Shell scripts on cron — works until it doesn't. No concurrency protection, no structured output, debugging means SSH and grep.
None of these treat observability as a first-class feature.
What Jobrail does differently
Every job execution is persisted. Jobrail writes a record to SQLite the moment a job starts running, and updates it when it finishes. Runs survive restarts. If the process dies mid-job, you'll see a status: running row with no finished_at — that's a signal, not silence.
The API makes everything programmable. List jobs, trigger a run, inspect history — all over REST. Your CI pipeline can kick off a backup verification. Your deploy script can check that health sweeps are passing. No SSH required.
Prometheus metrics are built in. jobrail_job_runs_total, jobrail_job_duration_seconds, jobrail_job_last_success_timestamp — exposed at /metrics, scraped by your existing monitoring stack, visualized in Grafana. A sample dashboard ships in the repo.
Writing a job is just Python. No framework magic. Define an async def run() function, return a JobResult, register it in one dict. That's the entire contract.
from jobs.base import JobResult
async def run() -> JobResult:
# your logic here
return JobResult(success=True, summary="all 5 endpoints healthy")
Quick start
With Docker Compose
git clone https://github.com/Paracosm-Lab/jobrail.git
cd jobrail
cp .env.example .env
docker compose up -d
Jobrail is running at http://localhost:8010. The two example jobs (hello-world and http-health-check) are already scheduled.
Verify it works
# List all registered jobs
curl http://localhost:8010/jobs | jq
# Trigger a job manually
curl -X POST http://localhost:8010/jobs/hello-world/trigger \
-H "Authorization: Bearer dev-token-change-me"
# Check run history
curl http://localhost:8010/jobs/hello-world/runs | jq
# View Prometheus metrics
curl http://localhost:8010/metrics | grep jobrail_
With Kamal
A deploy template is included at config/deploy.yml. Update the server IP, registry, and secrets, then:
kamal setup
Writing your first job
See the Writing Jobs guide for the full walkthrough.
API
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/health |
GET | No | Liveness check |
/metrics |
GET | No | Prometheus metrics |
/jobs |
GET | No | List all jobs with status and next run time |
/jobs/runs |
GET | No | Cross-job run history |
/jobs/{name}/trigger |
POST | Token | Trigger a job immediately |
/jobs/{name}/runs |
GET | No | Run history for a specific job |
/jobs/{name}/runs/{id} |
GET | No | Single run details |
/admin/info |
GET | Token | Service info, uptime, scheduler state |
/admin/maintenance |
POST | Token | Pause/resume all scheduled jobs |
Auth endpoints require Authorization: Bearer <SERVICE_TOKEN> header.
Configuration
See the Configuration Reference.
Monitoring
See the Monitoring guide for Prometheus metrics and Grafana setup.
How it compares
| Jobrail | Ofelia | Celery beat | k8s CronJob | |
|---|---|---|---|---|
| Persistent run history | Yes | No | Partial | No |
| REST API | Yes | No | No | No |
| Prometheus metrics | Yes | No | Partial | No |
| Manual trigger | Yes | No | Yes | Yes |
| Concurrency protection | Yes | Yes | Yes | Partial |
| External dependencies | None | None | Redis | Kubernetes |
| Grafana dashboard included | Yes | No | No | No |
Where it fits
Jobrail is not a replacement for your application's background job system. Sidekiq processes your user's uploads. Celery runs your ML pipeline. Those belong in your application.
Jobrail handles the operational layer alongside it — the cert checks, health sweeps, backup verification, and cleanup tasks that don't belong inside any one service. It's the scheduled ops work that every team does, and that most teams have cobbled together from cron entries, shell scripts, and hope.
Example jobs
See the Example Job Catalog.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jobrail-0.2.0.tar.gz.
File metadata
- Download URL: jobrail-0.2.0.tar.gz
- Upload date:
- Size: 207.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71318d2ac1bc3662d238278c24b33df5d4026b9c51de8832fba5a8c89294eb5e
|
|
| MD5 |
f0e7d322e5c398e1810137a461156597
|
|
| BLAKE2b-256 |
40af1432b1b4a5803b57e6750be6af441c70f831b04c54a5418f032948314ecb
|
Provenance
The following attestation bundles were made for jobrail-0.2.0.tar.gz:
Publisher:
publish.yml on Paracosm-Lab/jobrail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jobrail-0.2.0.tar.gz -
Subject digest:
71318d2ac1bc3662d238278c24b33df5d4026b9c51de8832fba5a8c89294eb5e - Sigstore transparency entry: 1565342333
- Sigstore integration time:
-
Permalink:
Paracosm-Lab/jobrail@c59979c17485a478462a2bda11704693392b070f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Paracosm-Lab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c59979c17485a478462a2bda11704693392b070f -
Trigger Event:
push
-
Statement type:
File details
Details for the file jobrail-0.2.0-py3-none-any.whl.
File metadata
- Download URL: jobrail-0.2.0-py3-none-any.whl
- Upload date:
- Size: 223.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0af87961510d400d1ed749f6166b6f4b6d3737f521054470197921ab04c3aa3
|
|
| MD5 |
ff06af2e8c3b079d898d939ce88cf44c
|
|
| BLAKE2b-256 |
9c0fbbfbb384bd1fe7cf64cfa28c47e1a2abd7f48f8db855af35e096086675f9
|
Provenance
The following attestation bundles were made for jobrail-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on Paracosm-Lab/jobrail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jobrail-0.2.0-py3-none-any.whl -
Subject digest:
d0af87961510d400d1ed749f6166b6f4b6d3737f521054470197921ab04c3aa3 - Sigstore transparency entry: 1565342348
- Sigstore integration time:
-
Permalink:
Paracosm-Lab/jobrail@c59979c17485a478462a2bda11704693392b070f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Paracosm-Lab
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c59979c17485a478462a2bda11704693392b070f -
Trigger Event:
push
-
Statement type: