Guides

Synchronize a Database

Share changes between machines and recover when their work conflicts.

Each machine keeps its own local database. Run silo push to publish changes and silo pull to receive them. Neither runs in the background.

Start with one machine whose data you want to share. You will need to configure storage; its storage and transfer charges depend on your provider.

Prepare the environment

Synchronization requires:

  • Litestream 0.5.12 or newer on PATH, or selected with LITESTREAM_PATH.
  • An S3-compatible bucket that supports conditional object writes.
  • Credentials available to both Silo and Litestream through the standard AWS environment or credential chain.
  • A stable, non-null primary key on every synchronized table.

For AWS, the usual environment includes AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION; temporary credentials may also need AWS_SESSION_TOKEN. Set AWS_ENDPOINT_URL_S3 for a custom S3-compatible endpoint. The active SQLite file must remain on local storage.

Silo does not store credentials in the database. The bucket must allow object reads, writes, and conditional writes for the configured prefix.

Initialize one authority

Start with an existing local database and an empty remote location. Replace the example bucket and path with your own:

silo sync init s3://my-bucket/silo/project
silo sync status
silo push

The initial status is ahead; the first push creates the remote checkpoint.

On another machine with the same repository identity, run the same sync init command. If the local database is absent and the remote exists, Silo restores the remote automatically.

Initialization needs one copy to start from:

Local database Remote checkpoint What to do
Exists Empty Run sync init, inspect ahead, then push.
Absent Exists Run sync init to restore it.
Exists Exists Initialization stops; choose an authority explicitly.
Absent Empty Create a schema first, then initialize from the local database.

When both copies exist, Silo does not merge them during setup. Inspect both copies and choose which one to use. These recovery commands require an unconfigured local database and a matching Git repository identity.

Enter your remote URL and the exact remote generation reported by the error:

printf 'Remote S3 URL: '
read -r SILO_REMOTE_URL
printf 'Remote generation to confirm: '
read -r REMOTE_GENERATION

Choose one of the following commands.

Preserve the local database as a recovery snapshot and install the remote:

silo sync adopt-remote "$SILO_REMOTE_URL" \
  --confirm "$REMOTE_GENERATION"

Preserve the old remote generation and publish the local database instead:

silo sync replace-remote "$SILO_REMOTE_URL" \
  --confirm "$REMOTE_GENERATION"

Both commands report the losing copy's location. If the confirmation no longer matches, inspect the new remote generation and make the decision again; do not retry blindly.

Use the shared-work loop

Pull before starting shared work and push after reviewing the local changes:

silo pull
# Read and write with Silo commands.
silo sync status
silo push

pull gets the current remote checkpoint and reapplies local work that still fits. push verifies a new checkpoint before publishing it. If changes conflict, Silo stops so you can decide what to keep.

Read synchronization status

Check state before and after shared work:

silo sync status
State Meaning
unconfigured This local database has no synchronization remote.
clean No local work is pending and the local base matches remote HEAD.
ahead Local synchronization transactions are pending on the current remote base.
behind Remote HEAD advanced and there is no pending local work.
diverged Remote HEAD advanced while local work is pending.
conflicted A pending local transaction could not be applied to the remote base.

Status also reports the local base generation, current remote generation, pending count, and conflict transaction ID when one exists.

Recover from a conflict

When pull or push reports sync_changeset_conflict, the active local database remains unchanged. Inspect the status and the operation named by the error before deciding what to keep:

silo sync status

Inspect the affected rows, query, or report with its normal show or get command. Save any values you need before discarding work.

Discard permanently removes the selected transaction's effects from the local database. This may affect several rows or tables if they were changed in one transaction. The command rebuilds from the remote and reapplies the other pending transactions.

To discard the transaction identified by the error:

printf 'Transaction id to discard: '
read -r TRANSACTION_ID
silo sync discard "$TRANSACTION_ID"
silo sync status

Check the new status. If another transaction conflicts, inspect it before taking further action. Once the conflict is resolved, write any reconciled values with the normal Silo commands and push again.

Warning

Never delete _silo_outbox rows or edit synchronization metadata directly. Those objects are part of the recovery protocol.

Serialize schema changes

Schema changes require a fully pulled base with no pending synchronization transactions. Pull, verify clean, make one schema change, and push it before continuing. For the issues table from Getting started, save this additive change as alter-issues.json:

{
  "add_columns": [
    {
      "name": "notes",
      "type": "text",
      "nullable": true,
      "comment": "Additional context for the issue."
    }
  ]
}

Then run:

silo pull
silo sync status
silo table alter issues --file alter-issues.json
silo table show issues
silo push

The table should now include the nullable notes column.

Schema changes are published as full checkpoints. They cannot be merged like row changes. If another schema publication wins, discard the losing schema transaction, pull the winning schema, and deliberately reapply a compatible change. Silo does not apply older-schema row changesets to a newer schema.

Prune old remote generations

Silo previews cleanup by default. Review unreferenced generations older than the seven-day grace period:

silo sync prune

Apply the reviewed default boundary only after checking the preview:

silo sync prune --apply

Use a longer grace period when publication or recovery procedures may remain active:

silo sync prune --older-than 30
silo sync prune --older-than 30 --apply

Prune never deletes the generation named by the HEAD it reads and stops if that pointer changes during cleanup. Applying cleanup permanently deletes objects under eligible generation prefixes; retain object-store versioning or backups when older checkpoints are part of your recovery policy.

For the checkpoint protocol, durability responsibilities, and current limits, see Synchronization model.