Russell Brooks
05/22/2026, 1:06 PMMarvin
05/22/2026, 1:06 PMMarvin
05/22/2026, 1:08 PMprefect-helm, a good production direction is:
1. disable the built-in PostgreSQL subchart
2. run PostgreSQL as its own app/chart
3. run PgBouncer as its own app/chart
4. point Prefect at PgBouncer, not directly at PostgreSQL
That gives you cleaner separation, easier upgrades, and better DB connection handling.
Important Prefect-specific note: if you put PgBouncer in transaction pooling mode, you should disable asyncpg/prepared statement caching for Prefect.
---
## High-level layout
In the same namespace, the apps would typically look like:
text
namespace: prefect
- prefect-server (Helm release: prefect/prefect-server)
- postgres (separate chart/app)
- pgbouncer (separate chart/app)
- redis (either part of prefect chart or separate)
- secrets/configmaps
- PVCs for postgres
Traffic flow:
text
Prefect API / background services
|
v
PgBouncer service
|
v
PostgreSQL service
---
## What Prefect expects
With the Helm chart, the key pattern is:
- postgresql.enabled: false
- sqlite.enabled: false
- provide DB connection info via the chart secret / env
- use host: pgbouncer... and port: 6432
Research indicates Prefect is fine with PgBouncer as long as the connection URL points to it. The main caveat is connection/pool compatibility settings.
---
## Recommended chart/app split
For your scale — ~5,000 flow runs/day — this is very reasonable:
### 1) PostgreSQL app
Use a dedicated PostgreSQL chart/app, for example:
- Bitnami PostgreSQL
- CloudNativePG
- Crunchy
- or your managed PostgreSQL if you later move off-cluster
If you want “fully fledged,” I would not use a hand-written Deployment for production unless you really want to own all HA/backup logic yourself. A real Postgres operator/chart is better.
### 2) PgBouncer app
Use a dedicated PgBouncer chart/app. This can be:
- Bitnami PgBouncer
- another PgBouncer chart
- or your own Deployment if you want full control
### 3) Prefect app
Use prefect/prefect-server, but configured against the external DB.
---
## Example architecture using Helm releases
text
helm release: postgres
chart: bitnami/postgresql
helm release: pgbouncer
chart: bitnami/pgbouncer
helm release: prefect
chart: prefect/prefect-server
All in namespace prefect.
---
## Prefect values example
Here is what the Prefect Helm values would look like conceptually.
yaml
postgresql:
enabled: false
sqlite:
enabled: false
secret:
create: true
name: prefect-db-connection
username: prefect_user
password: your-strong-password
host: pgbouncer.prefect.svc.cluster.local
port: "6432"
database: prefect
global:
prefect:
env:
- name: PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_SIZE
value: "5"
- name: PREFECT_SERVER_DATABASE_SQLALCHEMY_MAX_OVERFLOW
value: "10"
- name: PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_TIMEOUT
value: "30"
- name: PREFECT_SERVER_DATABASE_SQLALCHEMY_POOL_RECYCLE
value: "3600"
# Important for PgBouncer transaction pooling
- name: PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_STATEMENT_CACHE_SIZE
value: "0"
server:
replicaCount: 2
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
backgroundServices:
runAsSeparateDeployment: true
replicaCount: 1
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
redis:
enabled: true
If you already run Redis elsewhere, you could externalize that too.
---
## PostgreSQL app example
If using a separate PostgreSQL chart, a production-ish baseline would look like:
```yaml
auth:
username: prefect_user
password: your-strong-password
database: prefect
primary:
persistence:
enabled: true
size: 200Gi
resources:
requests:Marvin
05/22/2026, 1:08 PMFor *5,000 flow runs/day*, this is not an enormous workload for PostgreSQL, but retention/logging/task-run cardinality matters a lot. I’d treat these numbers as *starting points*, not exact sizing.
If you need HA, backups, PITR, and failover, I’d strongly lean toward an operator like CloudNativePG instead of a simple standalone chart.
---
## PgBouncer app example
A production-ish PgBouncer config for Prefect usually looks like this:
```yaml
config:
databases:
prefect:
host: postgres-postgresql.prefect.svc.cluster.local
port: 5432
dbname: prefect
pgbouncer:
pool_mode: transaction
max_client_conn: 1000
default_pool_size: 25
min_pool_size: 10
reserve_pool_size: 5
reserve_pool_timeout: 3
server_lifetime: 3600
server_idle_timeout: 600
server_connect_timeout: 15
stats_period: 60
replicaCount: 2
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
auth:
username: prefect_user
password: your-strong-password
The key setting is:
yaml
pool_mode: transaction
And then in Prefect:
yaml
- name: PREFECT_SERVER_DATABASE_SQLALCHEMY_CONNECT_ARGS_STATEMENT_CACHE_SIZE
value: "0"
That combination is the important compatibility piece.
---
## “Fully fledged” app picture
If you want a more realistic production picture, I’d think of it like this:
### PostgreSQL app responsibilities
- persistent storage
- backups / snapshots
- possibly replicas
- monitoring
- vacuum/autovacuum tuning
- versioned upgrades independent of Prefect
### PgBouncer app responsibilities
- absorb many short-lived app connections
- smooth spikes from API/background services/workers
- let Postgres keep a lower real connection count
- allow rolling restarts of Prefect with less DB churn
### Prefect app responsibilities
- API
- UI
- orchestration logic
- background services
- migrations
---
## Suggested scaling for your workload
For ~5,000 flow runs/day, I’d start around here:
### Prefect
- server.replicaCount: 2
- backgroundServices.runAsSeparateDeployment: true
- backgroundServices.replicaCount: 1 or 2 depending on HA goals
### PgBouncer
- 2 replicas
- default_pool_size: 20-25
- max_client_conn: 500-1000
### PostgreSQL
- 1 primary, 4-8 GiB memory, decent SSD-backed PVC
- 100-200 GiB storage to give yourself breathing room
- monitor growth before over-optimizing
Again, total DB size depends a lot on:
- flow run retention
- task run volume per flow
- event/log verbosity
- automations/events usage
---
## Example install order
1. install PostgreSQL
2. install PgBouncer
3. install Prefect configured to use PgBouncer
4. validate migrations/connectivity
5. cut over from the old subchart-backed DB
---
## Example Helm release flow
Conceptually:
bash
helm install postgres bitnami/postgresql -n prefect -f postgres-values.yaml
helm install pgbouncer bitnami/pgbouncer -n prefect -f pgbouncer-values.yaml
helm install prefect prefect/prefect-server -n prefect -f prefect-values.yaml
I’m not giving exact CLI flags beyond the basic shape since chart names/values differ a bit by chart source, but this is the pattern.
---
## Practical recommendations
### 1) Prefer a real Postgres chart/operator
If this is important production infrastructure, use a proper PostgreSQL chart/operator instead of raw manifests.
### 2) Use PgBouncer in front of Prefect
This is a good fit if you have multiple Prefect pods and bursty orchestration activity.
### 3) Keep DB credentials in K8s secretsMarvin
05/22/2026, 1:08 PMNate
05/22/2026, 4:10 PMRussell Brooks
05/25/2026, 6:20 PM