Sophia Ponte
10/29/2025, 11:38 PMMarvin
10/29/2025, 11:38 PMMarvin
10/29/2025, 11:43 PMPREFECT_SERVER_DATABASE_CONNECTION_URL (the PREFECT_API_* prefix was for 2.x). If you’re on 3.x, please switch to the PREFECT_SERVER_* names.
- You can set a single connection URL or set individual fields (PREFECT_SERVER_DATABASE_DRIVER, HOST, PORT, USER, NAME, PASSWORD). Using individual vars lets you rotate just the password secret.
- To pick up a new value (URL or password), restart the API pods.
Typical setup on EKS with the Helm chart
1) Put your DB URL (or password) in a Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: prefect-db
type: Opaque
stringData:
url: <postgresql+asyncpg://user:password@host:5432/dbname>
# or, if you prefer individual fields:
password: super-secret-password
2) Reference that Secret in the Helm chart values so the server pods get the env var
- The chart supports adding env vars to the server container via values.yaml; the exact key path depends on the chart version. Common patterns are an extraEnv list on the server/api container.
- Example (adjust the path to match your chart’s values—happy to help if you share your chart version or current values.yaml):
server:
api:
# add the env var from a Secret
extraEnv:
- name: PREFECT_SERVER_DATABASE_CONNECTION_URL
valueFrom:
secretKeyRef:
name: prefect-db
key: url
# or, if you are using individual fields instead of a full URL:
extraEnv:
- name: PREFECT_SERVER_DATABASE_DRIVER
value: postgresql+asyncpg
- name: PREFECT_SERVER_DATABASE_HOST
value: <http://mydb.example.rds.amazonaws.com|mydb.example.rds.amazonaws.com>
- name: PREFECT_SERVER_DATABASE_PORT
value: "5432"
- name: PREFECT_SERVER_DATABASE_USER
value: myuser
- name: PREFECT_SERVER_DATABASE_NAME
value: mydb
- name: PREFECT_SERVER_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: prefect-db
key: password
If you’re not sure about your chart’s exact values keys, you can check the chart here:
- prefect-helm GitHub repo
- Prefect docs: Helm deployment
3) Trigger pod restarts when the Secret changes
Options:
- Simple/manual: after rotating the Secret, restart the deployment
- kubectl rollout restart deploy/<your-prefect-server-api-deployment>
- Or helm upgrade --reuse-values <release> prefect/prefect-server (any change that updates pod template triggers a rollout)
- Automated: use a controller like Stakater Reloader to watch Secrets and restart pods when they change; most charts allow adding podAnnotations so Reloader can watch them. For example:
server:
api:
podAnnotations:
<http://reloader.stakater.com/auto|reloader.stakater.com/auto>: "true"
- If you use External Secrets Operator (ESO) to pull creds from AWS Secrets Manager, set its refresh interval and use either Reloader or a small CronJob that runs kubectl rollout restart after secret sync.
Notes and caveats
- Existing DB connections remain valid even after a password rotation, but new connections will fail until pods are restarted with the new password—so it’s best to restart promptly after rotation.
- If you’re considering AWS RDS IAM auth tokens: those tokens are short-lived and would still require a mechanism to refresh or restart pods. Prefect Server doesn’t generate IAM tokens itself; using a static password in a Secret plus an automated pod restart is usually simpler.
Helpful references
- Prefect Helm guide: docs-3.prefect.io/v3/advanced/server-helmMarvin
10/29/2025, 11:43 PMSophia Ponte
10/29/2025, 11:49 PM"2025.6.4170433". I am already using the external secrets operator, but the AWS secret it maps to is currently static.
Can you explain what you mean by "Existing DB connections remain valid even after a password rotation, but new connections will fail until pods are restarted with the new password—so it’s best to restart promptly after rotation."?Marvin
10/29/2025, 11:49 PMMarvin
10/29/2025, 11:51 PMPREFECT_SERVER_DATABASE_*). Those connections authenticate once when they are opened.
- When you change the user’s password in Postgres:
- Active sessions stay alive. Postgres does not kill existing sessions just because the password changed. Those connections keep working until they’re closed or terminated.
- New sessions require the new password. Any attempt to establish a new DB connection using the old password will fail with an authentication error.
How this shows up in Prefect
- Your running Prefect pods keep using their existing pooled connections, so things may look fine immediately after a rotation.
- As soon as Prefect needs to open a new DB connection (e.g., because of increased load, a dropped connection, pool recycling, or a DB restart), it will try with whatever credentials it loaded at startup. If the pods were not restarted after the secret was updated, those credentials are stale, and the new connection attempts will fail.
- These failures can appear sporadically at first (e.g., when the pool expands or a connection is recycled) and then escalate. By default, SQLAlchemy recycles connections periodically (e.g., pool_recycle ~ 3600s), so you may see more failures as old connections get closed and replaced.
Why restart promptly
- Restarting the pods right after the Secret is updated forces Prefect to rebuild the SQLAlchemy engine and load the new password, so all new connections authenticate successfully.
- If you delay the restart, you’ll get intermittent DB auth errors when new connections are needed.
Recommended patterns for your setup (EKS + Helm + ESO)
- Keep using ESO to sync the AWS Secret into a Kubernetes Secret.
- Automate rollouts on Secret changes so the restart happens as soon as ESO updates the Secret:
- Use a tool like Stakater Reloader and annotate the server pods so they roll when the Secret changes.
- Or add a small automation (e.g., a CronJob or CI/CD step) that runs a kubectl rollout restart of the API/services deployments when ESO updates the Secret.
- For zero/near-zero disruption:
- Do a rolling restart immediately after the secret update so new pods start with the new password while old pods continue using their existing connections.
- An even safer approach is to rotate to a different DB user: create a new user with the new password, update the Secret to use the new user, roll the pods, then remove the old user. This avoids any window where old pods might need to create new connections with an invalid password.
Relevant docs
- Prefect settings reference (PREFECT_SERVER_DATABASE_*): docs-3.prefect.io/v3/api-ref/settings-ref
- Helm deployment guide: docs-3.prefect.io/v3/advanced/server-helm
If you’d like, I can suggest exact values.yaml annotations for your chart version (2025.6.4170433) to wire up a Secret-watcher and perform rolling restarts on password changes.Sophia Ponte
10/29/2025, 11:52 PMMarvin
10/29/2025, 11:52 PMMarvin
10/29/2025, 11:54 PMkubectl rollout restart after ESO syncs the secret
- ECS automation (no proxy):
- Store DB creds in Secrets Manager, enable Secrets Manager rotation for your DB user
- EventBridge rule on rotation completion -> Lambda to force a new ECS deployment:
import boto3
def handler(event, context):
ecs = boto3.client('ecs')
ecs.update_service(cluster='my-cluster',
service='prefect-server',
forceNewDeployment=True)
- Best overall for frequent rotations:
- Use RDS Proxy with either:
- DB password stored in Secrets Manager and rotated by AWS; proxy updates automatically
- Or IAM auth to the proxy (no static password in Prefect)
- Prefect uses a stable connection string to the proxy; no app restarts needed on rotations
If you’d like, I can:
- Share exact values.yaml snippets to wire a Secret-watcher for your helm chart version (2025.6.4170433), or
- Sketch an ECS setup with Secrets Manager rotation + EventBridge/Lambda to auto-redeploy, or
- Outline an RDS Proxy connection string and settings for Prefect Server.Sophia Ponte
10/31/2025, 11:50 PMMarvin
10/31/2025, 11:50 PMMarvin
10/31/2025, 11:50 PMPREFECT_SERVER_DATABASE_CONNECTION_URL via `env`/`envFrom` (from a ConfigMap or a Secret), updating that resource does not change the env var inside running pods.
- Even if you mount a ConfigMap/Secret as a volume, Prefect Server does not watch files or hot-reload settings. It reads its settings at process start.
So you will still need to restart the server pods whenever the DB URL or password changes, regardless of ConfigMap vs Secret.
Recommended approach
- Keep credentials in a Secret (not a ConfigMap).
- Automate pod restarts when the Secret changes:
- Use Stakater Reloader to watch the Secret and roll the deployment automatically. Example pod annotation you can add to the Prefect Server API:
server:
api:
podAnnotations:
<http://reloader.stakater.com/auto|reloader.stakater.com/auto>: "true"
- Or trigger a rollout from your pipeline (e.g., after External Secrets Operator syncs the new value).
References
- Kubernetes ConfigMaps (env vars do not update in-place): https://kubernetes.io/docs/concepts/configuration/configmap/
- Stakater Reloader: https://github.com/stakater/Reloader
- Prefect Helm server guide: https://docs-3.prefect.io/v3/advanced/server-helm
- Prefect settings reference (PREFECT_SERVER_DATABASE_*): https://docs-3.prefect.io/v3/api-ref/settings-refSophia Ponte
10/31/2025, 11:52 PMMarvin
10/31/2025, 11:52 PMMarvin
10/31/2025, 11:55 PMPREFECT_API_URL.value() -> get_current_settings()....
- You can override settings programmatically within a scope using temporary_settings(...), which swaps in a new Settings instance for that scope. This does not re-read OS env; it applies the values you pass.
Implications for your server pods:
- If you change PREFECT_SERVER_DATABASE_CONNECTION_URL (or its password component) via a Secret/ConfigMap update, the running Prefect server will not pick it up until the process restarts and constructs a new Settings instance.
- Therefore, you should trigger a rolling restart of the server pods when the secret updates.
Docs:
- Settings and profiles overview: docs-3.prefect.io/v3/concepts/settings-and-profiles
- API reference for settings context and overrides: docs-3.prefect.io/v3/api-ref/python/prefect-settings-contextSophia Ponte
10/31/2025, 11:56 PMPREFECT_SERVER_DATABASE_CONNECTION_URL and fall back to a mounted .pgpass file?Marvin
10/31/2025, 11:56 PMMarvin
10/31/2025, 11:57 PMpostgresql+asyncpg). asyncpg does not use libpq, so it does not read .pgpass (or .pg_service.conf). It expects credentials to be provided explicitly (URL or discrete fields).
- Prefect’s settings system only reads from environment variables, profiles, and config files it knows about; it does not scan .pgpass.
What you can do instead:
- Keep using a Kubernetes Secret (populated by External Secrets Operator) and inject either:
- a single PREFECT_SERVER_DATABASE_CONNECTION_URL, or
- individual fields (PREFECT_SERVER_DATABASE_PASSWORD, etc.).
Then roll the pods when the Secret changes.
- If you must source from a file, use a small wrapper/entrypoint that reads the mounted .pgpass, exports the appropriate PREFECT_SERVER_DATABASE_* env vars, then execs the server. Example sketch:
#!/usr/bin/env sh
set -euo pipefail
# Parse the matching line: host:port:database:user:password
LINE=$(grep -m1 '^mydbhost:5432:mydb:myuser:' /mnt/pgpass/.pgpass || true)
PASSWORD=$(printf '%s' "$LINE" | awk -F: '{print $5}')
export PREFECT_SERVER_DATABASE_DRIVER=postgresql+asyncpg
export PREFECT_SERVER_DATABASE_HOST=mydbhost
export PREFECT_SERVER_DATABASE_PORT=5432
export PREFECT_SERVER_DATABASE_USER=myuser
export PREFECT_SERVER_DATABASE_NAME=mydb
export PREFECT_SERVER_DATABASE_PASSWORD="$PASSWORD"
exec prefect server start
This requires a custom image or command override and a volume mount for the pgpass file.
Best-practice options if you rotate credentials:
- Continue with ESO + Secret + automatic rollout on change (e.g., Stakater Reloader), since Prefect won’t hot-reload settings.
- Consider RDS Proxy so rotations are transparent to clients; Prefect connects to the proxy with a stable configuration.
References:
- Prefect settings reference (see PREFECT_SERVER_DATABASE_*): docs-3.prefect.io/v3/api-ref/settings-refSophia Ponte
11/06/2025, 7:55 PMMarvin
11/06/2025, 7:56 PMMarvin
11/06/2025, 7:59 PMSophia Ponte
11/06/2025, 8:00 PMMarvin
11/06/2025, 8:00 PMMarvin
11/06/2025, 8:02 PMSophia Ponte
11/06/2025, 8:03 PMMarvin
11/06/2025, 8:03 PMMarvin
11/06/2025, 8:06 PMget_client() to the Prefect Server that’s currently executing a long DB query will be interrupted when that server pod is terminated during the rollout.
- From the flow’s perspective, that specific client call will raise (connection reset/timeout or a 5xx). Prefect does not automatically retry arbitrary client calls on connection failure; you should add retries around any API calls your code makes.
What this means in practice
- After rollout, reissuing the same request will hit a new API pod and succeed (assuming the DB is healthy). The original long DB query on the terminated pod won’t complete.
- The rest of the flow continues; only that API call fails unless you retry it.
Recommendations to minimize interruption
- Run 2+ API replicas behind your Service and use a rolling update with maxUnavailable=0 and a PodDisruptionBudget so traffic drains before a pod is killed. A short preStop sleep and adequate terminationGracePeriodSeconds can help in-flight requests finish.
- Add retries/timeouts to your client calls in flow code. Example:
from prefect import task
from prefect.client.orchestration import get_client
import httpx
@task(retries=3, retry_delay_seconds=3)
async def call_prefect_api():
async with get_client(httpx_settings={"timeout": 30.0}) as client:
# Replace with the API call you need to make
# return await client.some_operation(...)
try:
...
except httpx.HTTPError:
# Let the task-level retries handle transient rollout errors
raise
- Keep API interactions idempotent where possible so retries are safe (reads/listing artifacts generally are).
- Expect a brief gap in UI logs right around the rollout; logs are batched and may be dropped if a batch send coincides with termination.
Docs for reference
- Self-hosted/HA guidance: Self-hosted Prefect
- Client settings (timeouts, retry knobs): Settings reference
- Rate limit behavior (the only automatic client retries): Rate limits
If you’re rolling server pods to pick up a rotated DB password, this approach (multiple replicas + rolling update) will avoid dropping in-flight calls in most cases. Do you currently have multiple API replicas configured via your Helm values? If you’d like, share your relevant values and I can suggest tweaks for safe rollouts.Sophia Ponte
11/06/2025, 8:08 PMMarvin
11/06/2025, 8:08 PMMarvin
11/06/2025, 8:13 PMhelm show values prefect/prefect-server --version 2025.6.4170433 | less
helm get values <release> -n <namespace> -o yaml
Common keys are either server.replicaCount or api.replicaCount (the chart has used both patterns across versions). Use whichever exists in your chart.
2) Set 2+ replicas via Helm
- If your chart uses server.* keys:
helm upgrade <release> prefect/prefect-server \
-n <namespace> \
--version 2025.6.4170433 \
--set server.replicaCount=2
- If it uses api.* keys:
helm upgrade <release> prefect/prefect-server \
-n <namespace> \
--version 2025.6.4170433 \
--set api.replicaCount=2
3) Add a PodDisruptionBudget (works regardless of chart keys) using extra objects in your values
extraObjects:
- apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: prefect-server-pdb
spec:
minAvailable: 1
selector:
matchLabels:
<http://app.kubernetes.io/name|app.kubernetes.io/name>: prefect-server
<http://app.kubernetes.io/instance|app.kubernetes.io/instance>: <release>
Note: adjust labels if your chart uses different ones; most Helm charts set those two labels.
4) Rolling updates and probes
- Most chart versions use a Deployment with RollingUpdate by default. If your chart exposes strategy knobs, set maxUnavailable=0:
# Use whichever block your chart exposes (server.strategy or api.strategy)
server:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
- Ensure readiness probes point to /api/readiness and liveness to /api/health on port 4200 (these are the Server endpoints). The chart generally sets these, but confirm in helm show values.
Optional for graceful shutdowns
- If supported by your chart, set a small preStop and adequate terminationGracePeriodSeconds so inflight requests finish:
server:
terminationGracePeriodSeconds: 60
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
If you want, paste the output of:
helm show values prefect/prefect-server --version 2025.6.4170433 | sed -n '1,200p'
and your helm get values <release> -n <ns> -o yaml, and I’ll pinpoint the exact keys for your chart version and provide a drop-in values.yaml.
References:
- Prefect self-hosted/HA: Self-hosted Prefect
- Helm chart docs: Prefect Server Helm guide
- Helm repo: prefect-helm
- K8s PDB: PodDisruptionBudgetSophia Ponte
11/06/2025, 8:22 PMMarvin
11/06/2025, 8:22 PMMarvin
11/06/2025, 8:23 PMserver:
replicaCount: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
terminationGracePeriodSeconds: 300
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
readinessProbe:
httpGet:
path: /api/readiness
port: 4200
livenessProbe:
httpGet:
path: /api/health
port: 4200
- PodDisruptionBudget (via extra objects)
extraObjects:
- apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: prefect-server-pdb
spec:
minAvailable: 1
selector:
matchLabels:
<http://app.kubernetes.io/name|app.kubernetes.io/name>: prefect-server
<http://app.kubernetes.io/instance|app.kubernetes.io/instance>: <release>
- ALB connection draining (example annotation; adjust for your setup)
service:
annotations:
<http://service.beta.kubernetes.io/aws-load-balancer-target-group-attributes|service.beta.kubernetes.io/aws-load-balancer-target-group-attributes>: deregistration_delay.timeout_seconds=300
- Add retries around client calls in your flows
from prefect import task
from prefect.client.orchestration import get_client
import httpx
@task(retries=3, retry_delay_seconds=5)
async def list_artifacts_with_retry(...):
async with get_client(httpx_settings={"timeout": 60.0}) as client:
try:
# Use pagination to avoid one giant query
# e.g., await client.read_artifacts(filter=..., limit=500, offset=...)
...
except httpx.HTTPError:
raise # let task retries handle transient rollout errors
Key takeawaysMarvin
11/06/2025, 8:23 PMSophia Ponte
11/06/2025, 8:35 PMMarvin
11/06/2025, 8:35 PMMarvin
11/06/2025, 8:36 PMterminationGracePeriodSeconds to exceed your longest request, and set your load balancer’s deregistration/drain timeout to match, or
- run 2+ replicas with draining so the API stays up during rollouts and in-flight requests can finish on the draining pod.
References:
- Kubernetes pod termination behavior: Pod termination
- Prefect self-hosted/HA guidance: Self-hosted PrefectSophia Ponte
11/06/2025, 9:32 PMMarvin
11/06/2025, 9:32 PMMarvin
11/06/2025, 9:33 PMSophia Ponte
11/06/2025, 9:47 PMMarvin
11/06/2025, 9:47 PMMarvin
11/06/2025, 9:49 PM