Skip to main content
Version: 2.0 prerelease

2.0 Prerelease Quickstart

2.0 prerelease

This guide uses the explicit 2.0 prerelease docs and exact published package versions. Stable 1.x remains the default documentation line.

Start the standalone Durable Workflow server, then choose PHP, Python, or Rust. Each route creates its own project and ends by reading a completed workflow and its durable result from the server. You do not need Laravel for any of these routes.

The SDKs automatically use their official Apache Avro dependencies for the default language-neutral payload envelope: apache/avro for PHP, avro for Python, and apache-avro for Rust.

Start the standalone server

You need Docker and curl. This source-free local server uses SQLite and a development token. Keep it running while you complete one language route.

export DW_SERVER_IMAGE=durableworkflow/server:0.2.659
export DW_AUTH_TOKEN=dev-token

docker volume create durable-workflow-quickstart

docker run --rm \
-v durable-workflow-quickstart:/app/database \
-e DW_AUTH_DRIVER=token \
-e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \
"$DW_SERVER_IMAGE" server-bootstrap

docker rm -f durable-workflow-server >/dev/null 2>&1 || true
docker run -d --name durable-workflow-server \
-p 8080:8080 \
-v durable-workflow-quickstart:/app/database \
-e DW_AUTH_DRIVER=token \
-e DW_AUTH_TOKEN="$DW_AUTH_TOKEN" \
"$DW_SERVER_IMAGE"

until curl -sf http://localhost:8080/api/ready >/dev/null; do sleep 1; done
curl -H "Authorization: Bearer $DW_AUTH_TOKEN" \
http://localhost:8080/api/cluster/info

The readiness request succeeds and cluster info identifies the local standalone server. Now follow exactly one route below.

PHP

Requirements: PHP 8.1 or newer and Composer. This is the framework-neutral durable-workflow/sdk package, not the embedded Laravel engine.

Install the SDK

mkdir durable-workflow-php-quickstart
cd durable-workflow-php-quickstart
composer require durable-workflow/sdk:0.1.5

Create the worker

The worker registers one workflow type and one activity type on its own task queue.

cat > worker.php <<'PHP'
<?php

declare(strict_types=1);

require __DIR__.'/vendor/autoload.php';

use DurableWorkflow\Client;
use DurableWorkflow\Worker;
use DurableWorkflow\Worker\ActivityContext;
use DurableWorkflow\Worker\WorkflowContext;

$client = new Client('http://localhost:8080', token: 'dev-token');
$worker = new Worker($client, 'quickstart-php');

$worker->registerActivity(
'quickstart.greet',
static fn (ActivityContext $context, string $name): string => "Hello, {$name}!",
);

$worker->registerWorkflow(
'quickstart.greeter',
static function (WorkflowContext $context, string $name): Generator {
$greeting = yield $context->activity('quickstart.greet', [$name]);

return ['greeting' => $greeting, 'language' => 'php'];
},
);

$worker->run();
PHP

Create the starter and result reader

This client starts a uniquely named workflow, waits for its selected run, and then describes the durable terminal state held by the server.

cat > start.php <<'PHP'
<?php

declare(strict_types=1);

require __DIR__.'/vendor/autoload.php';

use DurableWorkflow\Client;

$client = new Client('http://localhost:8080', token: 'dev-token');
$workflowId = 'quickstart-php-greeter-'.bin2hex(random_bytes(4));
$handle = $client->startWorkflow(
workflowType: 'quickstart.greeter',
workflowId: $workflowId,
taskQueue: 'quickstart-php',
input: ['PHP'],
);

$result = $handle->result(timeoutSeconds: 30);
$execution = $handle->describeSelectedRun();

echo "workflow_id={$execution->workflowId}\n";
echo "status={$execution->status}\n";
echo 'result='.json_encode($result, JSON_THROW_ON_ERROR)."\n";
PHP

Run it

php worker.php > quickstart-worker.log 2>&1 &
export QUICKSTART_WORKER_PID=$!
trap 'kill "$QUICKSTART_WORKER_PID" 2>/dev/null || true' EXIT

php start.php

kill "$QUICKSTART_WORKER_PID" 2>/dev/null || true
trap - EXIT

Success looks like this: status=completed and a result containing "greeting":"Hello, PHP!". You have run a standalone PHP worker and inspected its durable result without Laravel.

Continue with the PHP SDK guide, or return to the language choices and try a separate route against the same server.

Python

Requirements: Python 3.10 or newer. The program keeps the worker and client in one process, but they still communicate with the server through the public worker and control-plane APIs.

Install the SDK

mkdir durable-workflow-python-quickstart
cd durable-workflow-python-quickstart

python3 -m venv .venv
. .venv/bin/activate
pip install durable-workflow==0.4.99

Create the worker and starter

cat > greeter.py <<'PY'
import asyncio
import time

from durable_workflow import Client, Worker, activity, workflow


@activity.defn(name="quickstart.greet")
async def greet(name: str) -> dict:
return {"greeting": f"Hello, {name}!", "language": "python"}


@workflow.defn(name="quickstart.greeter")
class GreeterWorkflow:
def run(self, ctx, name):
return (yield ctx.schedule_activity("quickstart.greet", [name]))


async def main():
workflow_id = f"quickstart-python-greeter-{int(time.time())}"

async with Client(
"http://localhost:8080",
token="dev-token",
namespace="default",
) as client:
handle = await client.start_workflow(
workflow_type="quickstart.greeter",
task_queue="quickstart-python",
workflow_id=workflow_id,
input=["Python"],
)

worker = Worker(
client,
task_queue="quickstart-python",
workflows=[GreeterWorkflow],
activities=[greet],
)
await worker.run_until(workflow_id=workflow_id, timeout=30.0)

result = await handle.result(timeout=10.0)
execution = await handle.describe_run()

print(f"workflow_id={execution.workflow_id}")
print(f"status={execution.status}")
print(f"result={result}")


asyncio.run(main())
PY

python greeter.py

Success looks like this: status=completed and a result containing Hello, Python!. The last two SDK calls read the selected run's result and durable terminal state from the server.

Continue with the Python SDK guide, or return to the language choices and try a separate route against the same server.

Rust

Requirements: Rust 1.86 or newer. This example runs a native worker and client in one Tokio process.

Install the SDK

cargo new durable-workflow-rust-quickstart
cd durable-workflow-rust-quickstart
cargo add durable-workflow@=0.1.15
cargo add tokio --features macros,rt-multi-thread,time

Create the worker and starter

cat > src/main.rs <<'RS'
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use durable_workflow::{json, Client, Result, Worker, WorkflowResultOptions};

#[tokio::main]
async fn main() -> Result<()> {
let client = Client::builder("http://localhost:8080")
.token(Some("dev-token".to_string()))
.namespace("default")
.build()?;
let task_queue = "quickstart-rust";
let mut worker = Worker::new(client.clone(), task_queue);

worker.register_activity("quickstart.greet", |_context, arguments| async move {
let name = arguments
.get(0)
.and_then(|value| value.as_str())
.unwrap_or("Rust");
Ok(json!({"greeting": format!("Hello, {name}!"), "language": "rust"}))
});

worker.register_workflow("quickstart.greeter", |context, input| async move {
let name = input.get(0).and_then(|value| value.as_str()).unwrap_or("Rust");
context.activity("quickstart.greet", json!([name])).await
});

worker.register().await?;
let workflow_id = format!("quickstart-rust-greeter-{}", unique_suffix());
let handle = client
.start_workflow(
"quickstart.greeter",
task_queue,
&workflow_id,
json!(["Rust"]),
)
.await?;

let watcher = handle.clone();
worker
.run_until(async move {
loop {
if watcher.describe().await.is_ok_and(|run| run.is_terminal()) {
break;
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
})
.await?;

let result = handle.result(WorkflowResultOptions::default()).await?;
let execution = handle.describe_selected_run().await?;

println!("workflow_id={workflow_id}");
println!("status={}", execution.status.as_deref().unwrap_or("unknown"));
println!("result={result}");
Ok(())
}

fn unique_suffix() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
}
RS

cargo run

Success looks like this: status=completed and a JSON result containing "greeting":"Hello, Rust!". The example waits for its worker to finish the run, then reads the selected run's durable status and decoded result.

Continue with the Rust SDK guide.

Optional: embed the runtime in Laravel

Embedded Laravel is a separate first-party PHP deployment mode for applications that want workflow state, queue execution, configuration, and operator tooling inside their existing Laravel infrastructure. It installs durable-workflow/workflow; it does not use the standalone server or durable-workflow/sdk.

Start a fresh embedded application with the published package:

composer create-project laravel/laravel durable-workflow-laravel-quickstart
cd durable-workflow-laravel-quickstart
composer require durable-workflow/workflow:2.0.0-alpha.284@alpha
php artisan migrate
php artisan queue:work

Continue with Embedded Installation to configure a non-sync Laravel queue, then define and start an embedded workflow. Deployment Modes compares this specialized route with the standalone platform.

Next steps

Release qualification is intentionally separate from this first-success guide. The Platform Conformance Suite contains the exact artifact matrix, public-source checks, full execution transcripts, wall-clock criteria, teardown, and machine-readable quickstart contract used for certification.