🔥 Language-agnostic job orchestration over HTTP — PostgreSQL, Redis, or ValKey
Repo: github.com/hrodrig/gfire · Site: gfire.net · Spec: SPECIFICATIONS.md · Roadmap: ROADMAP.md · Security: SECURITY.md
GFire is a headless background job service: a standalone Go binary that runs a REST API and worker pool. Applications enqueue work over HTTP — they never import GFire as a library. Workers spawn external handler processes (cmd) against shared storage (PostgreSQL, Redis, or ValKey).
v1.0.3 — production-ready. Run
gfire server, enqueue via curl, inspect with CLI. Recurring cron validated (6-field);last_runtracked. NestedGFIRE_*env works for Compose. Ops console: GFireUI + BFF. See ROADMAP.md for Band 12 / Pipelines / Adoption.
- Quick start
- Config reference
- Curl cookbook
- Current status
- What GFire is
- Architecture
- Requirements
- Development
- PostgreSQL setup
- Redis / ValKey setup
- Compare
- Project docs
- Get involved
- License
No dependencies. One binary, one command.
make build # → bin/gfire
make server # → build + run (creates gfire.yaml from example if missing)Or manually:
go build -o bin/gfire ./cmd/gfire
cp gfire.example.yaml gfire.yaml
./bin/gfire server --config gfire.yamlDefault: in-memory backend, 4 workers, listening on 0.0.0.0:8080. No external services needed.
Health checks:
curl -sS http://127.0.0.1:8080/healthz # → {"status":"ok"}
curl -sS http://127.0.0.1:8080/readyz # → storage reachable probeAll configuration lives in gfire.yaml. Copy gfire.example.yaml as a starting point — every value shown is the default.
# Minimal config (in-memory, no auth):
storage:
backend: memory
server:
workers: 8 # goroutines pulling from queues
queues:
- critical
- defaultKey sections:
| Section | Purpose |
|---|---|
server |
Bind address, port, worker count, queue list, timeouts |
queue_limits |
Per-queue concurrency cap (0 = unlimited) |
storage |
Backend selection: memory, postgres, or redis |
auth |
Optional Bearer token authentication |
handlers |
Name → executable path mapping for job subprocesses |
heartbeat |
Server heartbeat interval, stale timeout, orphan grace period |
scheduler |
Poll interval and batch size for delayed/scheduled jobs |
cleanup |
How often expired terminal-state jobs are purged |
logging |
Level (debug/info/warn/error) and format (text/json) |
Environment overrides: Every key can be set as GFIRE_<PATH> with dots replaced by underscores:
GFIRE_STORAGE_BACKEND=postgres GFIRE_AUTH_TOKEN=secret ./bin/gfire serverFull reference: gfire.example.yaml — every field documented with defaults.
All examples assume the server is running on localhost:8080.
curl -sS -X POST http://127.0.0.1:8080/v1/jobs/enqueue \
-H 'Content-Type: application/json' \
-d '{"name":"echo","args":{"hello":"world"},"queue":"default"}'
# → {"job_id":"...","status":"enqueued","queue":"default"}With timeout and retry:
curl -sS -X POST http://127.0.0.1:8080/v1/jobs/enqueue \
-H 'Content-Type: application/json' \
-d '{"name":"echo","args":{"x":1},"timeout":"5m","retry_max":3}'curl -sS -X POST http://127.0.0.1:8080/v1/jobs/schedule \
-H 'Content-Type: application/json' \
-d '{"name":"echo","args":{"x":1},"enqueue_at":"2026-07-28T09:00:00Z"}'
# → {"job_id":"...","status":"scheduled","enqueue_at":"2026-07-28T09:00:00Z"}curl -sS http://127.0.0.1:8080/v1/jobs/JOB_ID
# → {"job":{...},"states":[...],"current_state":"Succeeded"}curl -sS 'http://127.0.0.1:8080/v1/jobs?limit=20'
curl -sS 'http://127.0.0.1:8080/v1/jobs?state=Failed&limit=10'curl -sS -X POST http://127.0.0.1:8080/v1/jobs/JOB_ID/cancel
# → {"status":"cancelling"}curl -sS -X POST http://127.0.0.1:8080/v1/jobs/JOB_ID/requeue
# → {"status":"enqueued"}curl -sS -X POST http://127.0.0.1:8080/v1/jobs/JOB_ID/continue \
-H 'Content-Type: application/json' \
-d '{"child_name":"echo","child_args":{"step":2},"condition":"on_succeeded"}'
# → {"status":"registered"}Conditions: on_succeeded (default), on_failed, on_any.
curl -sS http://127.0.0.1:8080/v1/queues # all queues + depth
curl -sS http://127.0.0.1:8080/v1/queues/default # single queue detail
curl -sS http://127.0.0.1:8080/v1/servers # active servers in cluster./bin/gfire job list --config gfire.yaml
./bin/gfire job list --state Failed --config gfire.yaml
./bin/gfire job get JOB_ID --config gfire.yaml
./bin/gfire job requeue JOB_ID --config gfire.yamlv1.0.3 — production-ready. Server, REST API, CLI, Prometheus metrics, all three storage backends. Recurring cron validation + last_run. Nested GFIRE_* env BindEnv. /healthz version/commit.
| Component | Status |
|---|---|
| Storage (memory, PostgreSQL, Redis/ValKey) | ✅ |
| Engine (workers, retry, cancel, DLQ, result capture) | ✅ |
| Continuations + recurring cron + orphan recovery | ✅ |
| REST API (enqueue, batch, schedule, list, cancel, continue, requeue, delete, recurring) | ✅ |
CLI (gfire server, job, migrate, queue, status) |
✅ |
Bearer auth, OpenAPI (/openapi.json), Prometheus (/metrics) |
✅ |
- Headless service — single binary, no embedded UI in v1
- HTTP + curl — apps never import GFire as a Go library
- Multi-backend — PostgreSQL (
SKIP LOCKED), Redis, ValKey - Horizontal scale — N peer nodes, shared storage, no Raft
- Continuations — chain jobs on success/failure
- Handlers — external binaries from YAML
cmd(any language)
See SPECIFICATIONS.md for the full design.
App (any language) --HTTP--> GFire API --> Engine / workers
|
v
Shared storage
(PG / Redis / ValKey)
|
v
Handler subprocess (cmd)
Job args are instruction cards (~1KB), not large payloads. Heavy data lives in S3/DB; the handler fetches and processes it.
- Go 1.26.5 (pinned in
go.mod) - Docker (optional) for PostgreSQL / Redis / ValKey via
docker compose golang-migrateCLI for PostgreSQL schema migrations
make help # list all targets
make all # fmt, vet, test, gocyclo, cover, build
make ci # fmt-check + vet + gocyclo + test
make security # govulncheck + gocyclo + grype
make cover # memory backend coverage (≥80% gate)
make version # build and print version + commit info
make install # install bin/gfire to $(go env GOPATH)/bin
make server # build + run daemonBinary output: bin/gfire.
- Tag
v*only frommainafter mergingdevelop. - Local bar before tagging:
make release-check(fmt, vet, test, cover ≥80% memory, gocyclo, govulncheck, grype,goreleaser check). - Tag workflow re-runs gates with
STRICT_RELEASE=1(adds docker-scan) before GoReleaser publishes binaries/GHCR. - Red gate = no image, no GitHub Release assets.
Set storage.backend: postgres in gfire.yaml, then apply schema migrations (required for Postgres; not used for Redis/ValKey):
make db-up # start postgres + redis + valkey via docker compose
make migrate-up # apply gfire schema (includes migration 002 for job results)
go test ./internal/storage/postgres/ -count=1
./bin/gfire server --config gfire.yamlDefault DSN: postgres://gfire:gfire@localhost:5432/gfire?sslmode=disable
(see Makefile / docker-compose.yml).
Set storage.backend: redis (or valkey) in gfire.yaml, then:
make db-up # starts redis on :6379, valkey on :6380
go test ./internal/storage/redis/ -count=1
./bin/gfire server --config gfire.yamlNo schema migrations. Redis/ValKey create keys at runtime (gfire:job:…, queues, sorted sets). make migrate-up / gfire migrate apply only to PostgreSQL — skip them for these backends.
ValKey is a drop-in Redis-compatible fork. Same config block, same addr:port field:
GFIRE_STORAGE_REDIS_ADDR=localhost:6380 ./bin/gfire serverSnapshot v1.0.0. GFire is a standalone service (HTTP API); the rest are embedded Go libraries.
| Axis | GFire | Asynq | River |
|---|---|---|---|
| Model | Standalone service | Go library | Go library |
| Enqueue | HTTP / curl ✅ | Go API | Go API |
| Storage | PG + Redis/ValKey ✅ | Redis | PostgreSQL |
| Handlers | External cmd ✅ |
In-process | In-process |
| HA | N peers, no Raft (partial) | Redis | PG SKIP LOCKED |
Full matrix (Sidekiq, Celery, Faktory, narratives): docs/compare.md · gfire.net/compare
| Doc | Purpose |
|---|---|
| SPECIFICATIONS.md | Behavior / architecture contract |
| ROADMAP.md | Weekly bands → v1.0.0 |
| CHANGELOG.md | Shipped changes per release |
| gfire.example.yaml | Configuration reference (every field documented) |
| docs/compare.md | GFire vs Asynq, River, Faktory, Sidekiq, Celery |
| AGENTS.md | Conventions for AI agents / contributors |
| CONTRIBUTING.md | How to contribute |
| SECURITY.md | Vulnerability reporting |
| CODE_OF_CONDUCT.md | Community standards |
- Open an issue for bugs or ideas
- PRs target
develop(see CONTRIBUTING.md) - Security: report privately via SECURITY.md — do not open a public issue for undisclosed vulns
MIT — Copyright (c) 2026 hrodrig.