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:
- Safe local editing with real content — pull production data, edit locally with actual site content, push when done
- 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:
- A setting
content_locked=trueis stored in the database - All CMS write operations (create, update, destroy) are blocked
- Editors see a message: “Content editing is temporarily locked”
- Regular visitors can still view the site normally
- 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
- Always lock before pulling — prevents conflicts from changes made during your work
- Keep sync sessions short — don’t leave production locked for extended periods
- Test locally first — verify changes work before pushing to production
- Communicate with the team — let editors know when you’re doing a sync