Skip to content
GardenCMS

Docs › Database

Database

Garden CMS stores pages, themes, collections, content blocks and settings in a relational database. DATABASE_URL chooses which:

DATABASE_URL Database
(unset) or sqlite:///data/garden.db SQLite file, relative to the app
sqlite:////var/lib/garden/garden.db SQLite file, absolute path
postgresql://user:pass@host:5432/garden PostgreSQL

Which one?

SQLite is a single file: nothing to install, trivial to back up, ideal for local work and small single-server sites.

PostgreSQL is the choice for managed or serverless hosting (Neon, Supabase, RDS…), several app instances, or STATELESS=true deployments. The app keeps a small connection pool tuned for serverless Postgres, whose compute suspends when idle.

Migrations

The schema is created and upgraded by migrations — the same ones for both databases:

uv run piccolo migrations forwards db

The container image runs this automatically on start. Migrations are idempotent: running them again does nothing.

Export and import

Your content can be written to a directory of plain files — and read back:

uv run python -m features.portable export content/
uv run python -m features.portable import content/            # dry run
uv run python -m features.portable import content/ --apply

An export contains every theme (template.html, style.css, head.html), page (.html body plus .json settings), collection and item, content block, setting and markdown document. Records refer to each other by slug, never by database id, and JSON is sorted — so an export is stable and reads well in git diff.

Uses:

  • Version your site in git — export, commit, review changes like code
  • Move between databases — export from SQLite, import into Postgres (or back)
  • Promote content — import a directory prepared elsewhere into production

Imports create and update; they never delete. Without --apply you only see the plan:

create theme      mycelium-term
update page       home
create markdown   docs/database.md

S3 credentials (s3_access_key_id, s3_secret_access_key) are left out of exports; pass --include-secrets if you really want them. Uploaded media files themselves stay in storage — the export lists their metadata.