Docs / Serverless Objects / Quick start
Quick start
Write a Durable Object counter, run it locally with sobjects dev, then deploy the Wrangler project to a bucket and serve it with sobjects up.
This walkthrough runs a Durable Object counter locally, then deploys it to a single node. You need the sobjects CLI from installation.
Write a Wrangler project
The code is written exactly as it would be for Cloudflare. sobjects reads wrangler.json or wrangler.jsonc, not wrangler.toml.
// wrangler.jsonc
{
"name": "hello",
"main": "src/index.js",
"compatibility_date": "2026-02-24",
"durable_objects": { "bindings": [{ "name": "COUNTER", "class_name": "Counter" }] },
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }]
}
// src/index.js
import { DurableObject } from "cloudflare:workers";
export class Counter extends DurableObject {
async increment() {
const count = ((await this.ctx.storage.get("count")) ?? 0) + 1;
await this.ctx.storage.put("count", count);
return count;
}
}
export default {
async fetch(request, env) {
const name = new URL(request.url).pathname.slice(1) || "default";
const count = await env.COUNTER.getByName(name).increment();
return new Response(`${name}: ${count}\n`);
},
};
Run it locally with sobjects dev
No bucket is needed. The first run downloads and verifies the pinned celld release.
sobjects dev . --port 8787
curl http://127.0.0.1:8787/alice # alice: 1
sobjects dev delegates to celld's local development server and does not start the ingress. Run sobjects dev --help to see celld's own options.
To run the same project in Node on the native engine instead:
sobjects dev . --engine native --port 8787
The native engine is for trusted code. See native engine and benchmarks.
Deploy to a node
Create sobjects.toml next to the project and point it at a bucket you control. The bucket must support conditional writes.
[celld]
bucket = "s3://my-bucket/serverless-objects"
# endpoint = "https://<account>.r2.cloudflarestorage.com" # for S3-compatible stores
# region = "auto"
Put the credentials in the environment, then deploy and start the node:
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...
sobjects deploy . # bundle the app and upload it to the bucket
sobjects up --detach # start the node on 127.0.0.1:8080
curl http://127.0.0.1:8080/alice
sobjects status
sobjects logs
sobjects down
State survives down and up because it lives in the bucket. Running nodes pick up a new deployment on their next poll.
What deploy does to your config
Before calling celld, dev and deploy parse the Wrangler config:
- Unknown top-level keys are rejected.
observabilityandupload_source_mapsare removed, with a warning.- A declared
build.commandruns first. - If
assets.directoryis missing in a project that uses@cloudflare/vite-plugin,vite buildruns and the generatedindex.htmlis located. Ambiguous output fails before celld starts.
deploy also fills a missing bucket, endpoint and region from the project's sobjects.toml. Explicit flags and environment variables take precedence. By default, deploy wraps your Durable Object classes in a small storage inspection hook for the dashboard. Pass --no-inspector to deploy the app unwrapped.
Next steps
- Running as a service: TLS ingress, the dashboard and systemd or launchd units.
- Porting from Cloudflare: moving an existing project.
- CLI reference