obsidian/wiki/concepts/payload-cms-push-dev-prod.md
2026-05-10 22:37:43 +01:00

100 lines
3.1 KiB
Markdown

---
name: payload-cms-push-dev-prod
description: Payload CMS postgresAdapter push:false vs push:true — conditional schema auto-push for dev, migration-only for prod
type: concept
---
# Payload CMS — `push` Config for `postgresAdapter` (Dev vs Prod)
## What `push` Does
In `payload.config.ts`, `postgresAdapter` has a `push` option:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
export default buildConfig({
db: postgresAdapter({
pool: { connectionString: process.env.DATABASE_URI },
push: true, // or false
}),
})
```
- **`push: true`** — Payload calls `drizzle-kit push` at startup, auto-syncing the schema to the DB. No migrations needed. Schema changes are applied immediately on every restart.
- **`push: false`** (default) — Payload uses `drizzle-kit migrate` style. Schema is only updated by running explicit migration files in `migrationDir`.
## The Critical Gotcha
**`push` defaults to `false`.**
After wiping the database volume (e.g., during development or staging reset), `push: false` means **tables are never created**. The admin panel returns SQL errors:
```
relation "users" does not exist
relation "payload_preferences" does not exist
```
This is a silent failure — there are no startup errors, just 500s when the admin panel tries to query tables that don't exist.
## The Correct Pattern
```ts
postgresAdapter({
pool: { connectionString: process.env.DATABASE_URI },
// Auto-push in dev (no migration workflow needed)
// Migration-only in prod (controlled, auditable, reversible)
push: process.env.NODE_ENV === 'development',
migrationDir: './migrations',
})
```
### Why Not Always `push: true`?
In production `push: true` means every deployment auto-alters the database schema. This is dangerous because:
- Column renames/drops are immediate and irreversible on the running DB
- No migration history → can't rollback schema changes
- Concurrent deployments can race on schema changes
### Why Not Always `push: false`?
In development `push: false` requires running `npx payload migrate` after every collection change. This adds friction and is easy to forget, leading to confusing "table not found" errors after volume resets.
## Running Migrations in Production
```bash
# Generate a migration from current schema changes
npx payload migrate:create --name add-status-to-leads
# Apply all pending migrations
npx payload migrate
# Check migration status
npx payload migrate:status
```
## Docker Compose Entrypoint Pattern
Run migrations before starting the server in production:
```dockerfile
# Dockerfile
CMD ["sh", "-c", "npx payload migrate && node server.js"]
```
Or in `docker-compose.yml`:
```yaml
services:
web:
command: sh -c "npx payload migrate && node .next/standalone/server.js"
depends_on:
db:
condition: service_healthy
```
## Related Scaffold Issue
When generating a new Payload + Next.js project, the scaffold (as of Payload 3.x) may omit `push: false` in the production config template. Always check `payload.config.ts` before deploying to production.
**Source:** daily/2026-05-09.md | 2026-05-09