DBOS Transact: durable Go workflows and queues backed by Postgres
Database-Backed Durable Golang Workflows
At a glance
- What is it?
- DBOS Transact is a Go library that adds durable workflows and queues on top of Postgres, so a program that crashes mid-operation resumes from its last completed step instead of starting over. You register ordinary functions as workflows or steps, and the framework handles checkpointing, recovery, exactly-once event processing and background task queues without a separate orchestrator or broker.
- Who is it for?
- Adopt DBOS Transact when your Go service has multi-step work that must survive a crash or a deploy, such as a payments flow or a resumable pipeline, and you would rather not run a separate orchestrator. Do not adopt it for work that finishes in milliseconds and never needs recovery, since the overhead is not free.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What DBOS is and the failure it is designed for
DBOS provides lightweight durable workflow orchestration on top of Postgres. The README's framing of the alternative is useful: instead of managing your own workflow orchestrator or task queue system, you can use DBOS to add durable workflows and queues to your program in just a few lines of code.
The question the library is built to answer is when should I use DBOS, and the answer is when your application needs to reliably handle failures. The two examples given are a payments service that must reliably process transactions even if servers crash mid-operation, and a long-running data pipeline that needs to resume seamlessly from checkpoints rather than restart from the beginning when interrupted.
The argument for it is about cost rather than capability. Handling failures is costly and complicated, requiring complex state management and recovery logic as well as heavyweight tools like external orchestration services. The claim is that you can get the same behaviour by annotating your code to checkpoint it in Postgres and automatically recover from any failure.
That is a meaningfully smaller dependency than a workflow engine. There is no separate broker, no separate orchestrator service, no control plane to operate. The state is in the database you already run, which also means the state is backed up, replicated and observable with the tooling you already have for that database.
The repository carries a chaos_tests/ directory alongside the integration/ and docs/ directories, and the topic list names durable-execution, orchestration and workflow-automation. The project site is dbos.dev and the documentation is at docs.dbos.dev. The licence is MIT and the release cadence is fast, with v1.7.0, v1.6.0 and v1.5.0 landing in October, the first two weeks of October and late September 2026.
Durable workflows: registering ordinary Go functions
DBOS workflows make your program durable by checkpointing its state in Postgres. If your program ever fails, when it restarts all your workflows automatically resume from the last completed step. You add durable workflows to your existing Golang program by registering ordinary functions as workflows or running them as steps.
The example shows the whole shape. A workflow function takes a dbos.Context and returns a value and an error, and inside it calls dbos.RunAsStep for each unit of work:
func workflow(dbosCtx dbos.Context, _ string) (string, error) {
_, err := dbos.RunAsStep(dbosCtx, stepOne)
if err != nil {
return "", err
}
return dbos.RunAsStep(dbosCtx, stepTwo)
}The steps themselves are ordinary functions taking a plain context.Context, so the code you are checkpointing does not have to know it is being checkpointed. That is the ergonomic claim of the library, and it holds: the step functions are just functions.
The setup is a context, a workflow registration and a launch:
ctx, err := dbos.NewContext(context.Background(), dbos.Config{
DatabaseURL: os.Getenv("DBOS_SYSTEM_DATABASE_URL"),
AppName: "myapp",
})
if err != nil {
panic(err)
}
// Register a workflow
dbos.RegisterWorkflow(ctx, workflow)
// Launch DBOS
err = dbos.Launch(ctx)
if err != nil {
panic(err)
}
defer dbos.Shutdown(ctx, 2 * time.Second)The database URL comes from the environment, which means the same binary can point at a different database per environment without a rebuild. The AppName is how the framework namespaces its state, which matters when several services share one database.
Running a workflow and getting its result goes through a handle, and the result retrieval is a separate call that blocks:
handle, err := dbos.RunWorkflow(ctx, workflow, "")
if err != nil {
panic(err)
}
res, err := handle.GetResult()The handle is what makes the queue and event features composable, since a queued workflow also returns a handle and the caller waits on it the same way.
Durable queues and flow control
DBOS queues help you durably run tasks in the background. When you enqueue a workflow, one of your processes will pick it up for execution. The framework manages execution of your tasks: it guarantees that tasks complete, and that their callers get their results without needing to resubmit them, even if your application is interrupted.
That guarantee is what distinguishes this from an ordinary in-process goroutine pool. The caller submits work, gets a handle, and can wait for the result at any later point, including after a restart, because the task's outcome is recorded in Postgres rather than living in a goroutine's stack.
The queue also brings flow control. You can limit the concurrency of your tasks on a per-queue or per-process basis. You can set timeouts for tasks, rate limit how often queued tasks are executed, deduplicate tasks, or prioritize tasks. Each of those is a database-level operation in this design, which means they are configured and observed the same way as everything else.
Adding a queue is two calls. You register the workflow as usual and register a queue by name:
dbos.RegisterWorkflow(ctx, task)
queue, err := dbos.RegisterQueue(ctx, "queue")
if err != nil {
panic(err)
}Then the work is submitted with the queue attached:
handle, err := dbos.RunWorkflow(ctx, task, i, dbos.WithQueue(queue))The README notes that queues do not require a separate queueing service or message broker, just Postgres.
The README also lists the situations workflows are particularly useful for: orchestrating business processes so they seamlessly recover from any failure, building observable and fault-tolerant data pipelines, and operating an AI agent, or any application that relies on unreliable or non-deterministic APIs. That last case is worth dwelling on, because it is the one the library's own users find most valuable: an agent that calls a flaky model and might get a different answer on a retry is exactly the situation where resuming from the last checkpoint, rather than restarting, changes the outcome.
Exactly-once event processing and the other primitives
The third feature area in the README is exactly-once event processing, used to build reliable webhooks, event listeners or Kafka consumers by starting a workflow exactly-once in response to an event.
The phrase needs care, and it is worth being precise about what it can and cannot mean. Exactly-once here means the workflow starts once per event, with the record of what has been processed kept in Postgres. It cannot make a third-party API call exactly-once, because that depends on the other end, and it cannot undo a side effect that already happened. What it gives you is that a redelivered webhook does not start a second workflow, which is the part that actually breaks in production.
Beyond that, the README lists a set of Postgres-backed primitives that make it easier to write and operate reliable code: durable queues, notifications, scheduling, event processing, and programmatic workflow management.
The dependency list in go.mod supports the reading that this is a Postgres library with some orchestration on top. The direct dependencies are pgx v5 for the database driver, pgerrcode for PostgreSQL error codes, uuid for identifiers, robfig/cron v3 for scheduling, cobra and viper for the command line and configuration, and testify for tests. There is also modernc.org/sqlite, which is a pure-Go SQLite implementation, and go.uber.org/goleak, which detects leaked goroutines in tests.
That last one is a meaningful signal about how the library treats its own concurrency. A framework whose core promise is that work resumes correctly after a crash has to be sure it is not leaking goroutines in the normal case, and a test dependency that asserts exactly that is a reasonable thing for the maintainers to insist on.
The go.mod requires Go 1.25.0 with a toolchain directive of go1.26.9.
Limits: Postgres is mandatory, and durability is not idempotency
The limits are visible in the design.
The first is that Postgres is not optional. The library's entire state lives there, and the configuration takes a DatabaseURL and nothing else. There is no pluggable backend, so if your infrastructure does not run Postgres, this is not an option, and if you run many services against one database you are relying on the AppName to keep them apart.
The second is that durability is not idempotency. A workflow that resumes from its last completed step will re-run the step that was in flight when the crash happened. That is correct behaviour for a durable workflow, since the failed step did not complete, but it means the step itself must be safe to run twice. Any side effect inside a step, a charge, an email, an upstream write, needs an idempotency key or a deduplication strategy of its own. The framework gives you the resume, not the exactly-once side effect, and the README's own examples of workflows being particularly useful are careful to describe recovery rather than exactly-once semantics.
The third is that the overhead is real. Every step is checkpointed, which means a write to Postgres per step, and the launch and shutdown calls mean the process has a lifecycle tied to the framework. For a workflow that finishes in milliseconds and never fails, that is a cost with no benefit, and the library is not the right tool for it.
The fourth is the coupling to your code shape. Steps take a plain context.Context and workflows take a dbos.Context, so the functions you want to be durable have to be reachable from a registration call, and a workflow that calls out to code you do not control is a workflow whose behaviour you do not fully control.
The fifth is the version line. The releases are 1.x and moving quickly, with two releases in October 2026 and one in late September, so the API is evolving and pinning is worth doing. The library is young and the durability guarantees are recent work.
Finally, the operational assumption. Recovery means the framework restarts your workflows on startup, which means a process that dies in a loop will resume in a loop, and someone has to watch for that. Chaos testing is part of the repository, which suggests the maintainers take that seriously, but it is still your responsibility to know your workflows are stuck rather than progressing.
DBOS Transact against Temporal and against a hand-rolled state machine
Two comparisons make the choice clearer.
The first is against Temporal, the established name in durable execution. The difference in approach is operational surface. Temporal gives you a dedicated server, a history service, workers that poll for tasks, visibility for querying workflow state, and a client library that is considerably larger than what you need for a service with a handful of long-running operations. DBOS gives you a library and a Postgres database.
The trade is that with Temporal you get a mature system with an ecosystem, and with DBOS you get something you can read in an afternoon because most of it is the database you already run. For a team that wants durable execution without adding infrastructure, that is a strong argument. For a team that has already standardised on Temporal, or that needs features DBOS does not offer, the comparison ends quickly.
The second comparison is against writing the state machine yourself. This is the option that looks cheapest at the start and gets expensive. You end up writing a status column, a retry policy, a scheduler and a way to resume after a crash, and then you maintain it, and the bugs in it are the kind that appear only during an incident. The README's framing of handling failures as costly and complicated, requiring complex state management and recovery logic, is a fair description of that path.
The honest middle case is that many services do not need durability at all. If a failed operation can simply be retried by the caller, and the operation is not doing anything irreversible, then a plain function call and a retry loop are the right answer, and a workflow framework is overhead.
DBOS earns its place in the specific case where the work is multi-step, takes long enough that restarting from the beginning is unacceptable, and must survive a process death rather than just an error. The payments example in the README is exactly that case, and it is worth asking whether yours is the same shape before you add a framework.
Editorial conclusion
Adopt DBOS Transact when your Go service has multi-step work that must survive a crash or a deploy, such as a payments flow or a resumable pipeline, and you would rather not run a separate orchestrator. Do not adopt it for work that finishes in milliseconds and never needs recovery, since the overhead is not free. Verify first that Postgres is already part of your stack, since the framework's entire state lives there, that your Go version is 1.25 or newer, and that the semantics of exactly-once event processing match what your webhook senders actually guarantee.
Frequently asked questions
What does DBOS Transact add to a Go program?
Durable workflows, durable queues and exactly-once event processing, all backed by Postgres. You register ordinary functions as workflows or run them as steps with dbos.RunAsStep, and the framework checkpoints state in Postgres so that after a crash the workflows resume from the last completed step, with no separate orchestrator or message broker required.
What are the requirements for running DBOS Transact?
Postgres is required, since the database holds all workflow and queue state, and the configuration takes a DatabaseURL and an AppName. The go.mod requires Go 1.25.0 with a toolchain directive of go1.26.9, and the driver is pgx v5. The licence is MIT.
What can DBOS queues do beyond running background tasks?
They provide flow control: you can limit the concurrency of tasks per queue or per process, set timeouts for tasks, rate limit how often queued tasks are executed, deduplicate tasks, and prioritise them. When you enqueue a workflow, one of your processes picks it up, and callers get their results without resubmitting even if the application is interrupted.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dbos-inc-dbos-transact-golang)Was this useful?