# Push vs Pull Sync in Cloudflare Computer: When to Use Each Operation

> Understand push vs pull sync in Cloudflare Computer. Learn when to stream changes to or retrieve changes from your Durable Object for optimal performance.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: deep-dive
- Published: 2026-08-14

---

**Push sync streams changes from the Durable Object to the container before execution; pull sync retrieves container writes back to the Durable Object after execution.**

Cloudflare Computer maintains two copies of every workspace: a **SQLite-backed copy in the Durable Object (DO)** that persists across restarts, and a **FUSE-mounted copy in the ephemeral container** that only lives for the duration of a process. The `push` and `pull` sync operations keep these copies consistent using incremental, bidirectional synchronization with monotonic revision watermarks. This guide explains how each operation works, where it's implemented in the source code, and exactly when to use each one.

## What Push Sync Does (DO → Container)

Push sync transfers every `ChangeEntry` that the DO has generated since the last successful push. The operation coalesces multiple writes to the same path—five writes to one file become a single entry on the wire.

### How Push Sync Works

The implementation in [[`packages/rpc/src/sync-driver.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts)](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts) follows this sequence in the `pushOnce` function:

1. **Probe** the container for chunk hashes it already owns via `hasObjects`
2. **Stream missing chunks** via `pushObjects`
3. **Stream change entries** via the `push` RPC
4. **Confirm application**: The container returns an `appliedPushCursor` that must cover the revision the DO claimed to push; the DO then advances its `pushRev` watermark

```typescript
// Push DO changes to container before execution
import { pushOnce } from "@cloudflare/computer/rpc";

// db: local DO database
// remote: container RPC stub
await pushOnce(db, remote);

// Or use the high-level workspace API
await workspace.push();  // also pulls pending changes first

```

### When to Use Push Sync

- **Before `exec`**: Push pending changes so the container sees the latest state when the command runs
- **Explicit synchronization**: Force a push without executing a command, such as after batch writes from a script

## What Pull Sync Does (Container → DO)

Pull sync retrieves every change the container has produced since the last successful pull. Each entry carries the hashes of file chunks it references; the DO probes for missing chunks and fetches only those.

### How Pull Sync Works

The `pullOnce` and `pullOnceImpl` functions in [[`packages/rpc/src/sync-driver.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts)](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts) execute this flow:

1. **Fetch changes**: Call `fetchChanges({ after })` to receive a stream of `ChangeEntry`s plus `currentCursor` and `appliedPushCursor`
2. **Batch and apply**: Process up to `PULL_BATCH_SIZE` (256) entries at a time—probe for missing chunks with `hasObjects`, fetch them with `fetchObjects`, then apply the batch via `applyChanges`
3. **Advance cursor**: After each batch, write the new fetch cursor with `writeFetchCursorIfAhead`

```typescript
// Pull container writes back into the DO
import { pullOnce } from "@cloudflare/computer/rpc";

await pullOnce(db, remote);

// Or use the high-level workspace API
await workspace.pull();  // used after exec returns

```

### When to Use Pull Sync

- **After `exec`**: The container may have written files via FUSE; pull those writes to persist them in SQLite
- **Explicit synchronization**: Catch up the DO state without waiting for the next command execution

## The Pull-Then-Push Pattern: `tick`

The standard sync loop performs **pull first, then push** in a single `tick` operation. Pulling first ensures remote changes are applied before the DO calculates what still needs to push, preventing redundant uploads of identical entries.

```typescript
// Full sync tick (pull then push)
import { tick } from "@cloudflare/computer/rpc";

const { pulled, pushed } = await tick(db, remote);
console.log(`Applied ${pulled.applied} entries, pushed ${pushed} entries`);

```

Use `tick` for deterministic synchronization in tests or custom workflows where you need complete reconciliation in one step.

## Push vs Pull: Decision Reference

| Situation | Operation | Method |
|-----------|-----------|--------|
| Wrote files **inside the container** (build artifacts, tool output) | **Pull** | `workspace.pull()` or `pullOnce` |
| Mutated files **through the DO API** (`ws.fs.writeFile`) | **Push** | `workspace.push()` or `pushOnce` |
| About to run `exec` that reads files | **Push** then execute | `pushOnce` or `workspace.push()` |
| Just finished `exec` that may have written files | **Pull** | `workspace.pull()` or `pullOnce` |
| Need deterministic full sync | **Both** | `tick()` |

## Key Implementation Details

### Watermark Tracking

The DO maintains two critical counters in its SQLite schema ([[`packages/dofs/src/schema/sync.ts`](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/schema/sync.ts)](https://github.com/cloudflare/computer/blob/main/packages/dofs/src/schema/sync.ts)):

- **`pushRev`**: Last revision successfully pushed to the container
- **`fetchCursor`**: Last revision successfully pulled from the container

These watermarks enable incremental sync—only deltas transfer across the network.

### Coalescing and Batching

- **Push**: Per-path coalescing collapses multiple writes to identical paths
- **Pull**: Fixed batch size of 256 entries (`PULL_BATCH_SIZE`) balances throughput with memory usage

## Summary

- **Push sync** (DO → container) prepares the container for execution by streaming pending DO changes first
- **Pull sync** (container → DO) persists container output by retrieving FUSE-mounted writes back to durable SQLite storage
- The `tick` function combines both operations in the optimal order (pull, then push) for complete reconciliation
- Watermarks (`pushRev`, `fetchCursor`) enable efficient incremental synchronization without full state transfers

## Frequently Asked Questions

### What happens if I call `push` without pulling first?

You risk overwriting container writes that haven't been persisted to the DO yet. The DO's `pushRev` watermark may claim revisions that the container has already modified locally, causing synchronization conflicts. Always pull first unless you're certain the container hasn't written anything.

### How does Cloudflare Computer handle large files during sync?

The sync protocol operates on **content-addressed chunks**, not whole files. Both push and pull first probe (`hasObjects`) which chunks the remote side already possesses, then transfer only missing chunks (`pushObjects`/`fetchObjects`). This deduplication happens automatically regardless of which operation you're running.

### Can I use `workspace.push()` and `workspace.pull()` in my own scripts?

Yes. These high-level APIs wrap the lower-level `pushOnce` and `pullOnce` functions from `@cloudflare/computer/rpc`. Note that `workspace.push()` internally performs a pull first to merge any pending container changes, while `workspace.pull()` performs only the pull operation.

### Where is the sync protocol documented architecturally?

The design document at [[`docs/02_sync_protocol.md`](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md)](https://github.com/cloudflare/computer/blob/main/docs/02_sync_protocol.md) covers watermark reconciliation, the push/pull lifecycle, and protocol invariants in detail. The reference implementation lives in [[`packages/rpc/src/sync-driver.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts)](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.ts), with comprehensive tests in [[`packages/rpc/src/sync-driver.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.test.ts)](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/sync-driver.test.ts).