Content Sync

Lean CMS uses SQLite, which means the entire database is a single file. This enables a clean sync workflow: pull the production database locally, make changes, and push back — without the search-and-replace nightmares of MySQL dumps.

Overview

The sync system solves two problems:

  1. Safe local editing with real content — pull production data, edit locally with actual site content, push when done
  2. No simultaneous edits — a content lock blocks CMS writes on production while you’re working locally, preventing conflicts

Quick start

# 1. Lock production and pull database
bin/rails lean_cms:sync:start

# 2. Make your changes locally
RAILS_ENV=production_local bin/rails console

# 3. Push changes and unlock production
bin/rails lean_cms:sync:finish

Manual workflow

For more control over each step:

# 1. Lock production (prevents editors from making changes)
kamal cms-lock

# 2. Pull the database locally
bin/rails lean_cms:sync:pull

# 3. Make changes locally using production_local environment
RAILS_ENV=production_local bin/rails console
RAILS_ENV=production_local bin/rails server

# 4. Push your changes to production
bin/rails lean_cms:sync:push

# 5. Unlock production
kamal cms-unlock

Kamal commands

Command Description
kamal cms-lock Lock content editing on production
kamal cms-unlock Unlock content editing on production
kamal cms-status Check current lock status

Rake tasks

Server-side tasks (run on production)

Task Description
lean_cms:sync:lock Lock content editing
lean_cms:sync:unlock Unlock content editing
lean_cms:sync:status Show lock status

Local tasks

Task Description
lean_cms:sync:pull Pull production database to local
lean_cms:sync:push Push local database to production
lean_cms:sync:start Lock production and pull database
lean_cms:sync:finish Push database and unlock production
lean_cms:sync:stage Copy development DB to production_local
lean_cms:sync:workflow Print full workflow help

How content locking works

When content is locked:

  1. A setting content_locked=true is stored in the database
  2. All CMS write operations (create, update, destroy) are blocked
  3. Editors see a message: “Content editing is temporarily locked”
  4. Regular visitors can still view the site normally
  5. The lock reason and timestamp are recorded

Locking with a custom reason

REASON="Deploying new content structure" kamal cms-lock

Local development environment

When you pull the production database, it’s saved as storage/production_local.sqlite3. Use the production_local Rails environment to work with it:

# Rails console
RAILS_ENV=production_local bin/rails console

# Rails server (runs on default port)
RAILS_ENV=production_local bin/rails server

# Run migrations on local copy
RAILS_ENV=production_local bin/rails db:migrate

Configure production_local in config/database.yml:

production_local:
  adapter: sqlite3
  database: storage/production_local.sqlite3
  pool: 5
  timeout: 5000
  pragmas:
    journal_mode: wal
    synchronous: normal

File locations

File Location
Local database storage/production_local.sqlite3
Production database /var/lib/docker/volumes/app_storage/_data/production.sqlite3

Safety features

Automatic backups — Before pushing, a timestamped backup is created on the server:

production.sqlite3.backup.20241203_120000

WAL checkpoint — Before push operations, the local database is checkpointed to ensure all WAL changes are written to the main file.

Container restart — The production container is stopped before database operations and restarted after, ensuring a clean database state.

Troubleshooting

“Content is locked” but you didn’t lock it

Someone may have started a sync and not finished. Check status and unlock if safe:

kamal cms-status
kamal cms-unlock

Database locked error

SQLite WAL files may be stale. The sync tasks automatically clean these up, but if needed:

ssh root@your-server "rm -f /var/lib/docker/volumes/app_storage/_data/production.sqlite3-{shm,wal}"

Pull/push fails with SSH error

Ensure 1Password SSH agent is running and your key is available:

ssh-add -l

Best practices

  1. Always lock before pulling — prevents conflicts from changes made during your work
  2. Keep sync sessions short — don’t leave production locked for extended periods
  3. Test locally first — verify changes work before pushing to production
  4. Communicate with the team — let editors know when you’re doing a sync