How to Make Your App Wait for Postgres on Startup

When your app connects to Postgres at startup, it races the database. If Postgres isn't ready yet, the connect raises immediately and the process dies. Adding a retry-with-backoff loop is the standard fix - the app keeps trying for a few seconds rather than crashing on the first refused connection.

DevOps Engineerpostgresflaskrace-condition

Why the app crashes on startup

A Flask (or any Python) app that calls psycopg2.connect() at module load time runs into a race condition: if Postgres is still starting when the app process launches, the connect attempt gets Connection refused and raises immediately. The process exits before it ever serves a request.

This is especially common in Docker Compose and Kubernetes where the app container starts in parallel with the database container. Even a depends_on check in Compose only waits for the container to be running, not for Postgres to be accepting connections.

The fix: retry with exponential backoff

Wrap the startup connect in a loop that retries with increasing delays:

import time
import psycopg2

DB_CONFIG = {
    "host": "localhost",
    "port": 5432,
    "dbname": "appdb",
    "user": "appuser",
    "password": "apppass",
}

def wait_for_db(retries=15):
    for attempt in range(retries):
        try:
            conn = psycopg2.connect(**DB_CONFIG)
            conn.close()
            print("Database ready.")
            return
        except psycopg2.OperationalError as e:
            wait = min(2 ** attempt, 5)
            print(f"DB not ready (attempt {attempt + 1}): {e} - retrying in {wait}s")
            time.sleep(wait)
    raise SystemExit("Could not connect to database after retries")

wait_for_db()

The key points:

Verify it works

# Check Postgres is ready separately
pg_isready -U postgres

# Check the app logs for retry lines
cat /var/log/app.log

# Confirm the app is up and serving
curl http://localhost:8080/health

Going further in production

App-level retry is the safety net, not the only layer. In Kubernetes, pair it with:

These layers complement each other: init containers prevent the startup race, probes handle mid-run failures, and the app-level retry catches anything the init container misses.

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

Why does my app crash immediately on startup with 'connection refused' from Postgres?

The app is calling psycopg2.connect() before Postgres is ready to accept connections. It's a startup race condition - the app process starts, tries to connect, gets refused, and exits. The fix is a retry loop with backoff rather than a single connect attempt.

How do I make Python wait for Postgres to be ready?

Wrap the startup connect in a loop that catches psycopg2.OperationalError, waits with exponential backoff (e.g. min(2**attempt, 5) seconds), and gives up after a reasonable number of retries. This lets the app survive a few seconds of Postgres startup time.

Does Kubernetes depends_on or init containers replace app-level retry?

Not entirely. Init containers can block the app until pg_isready passes, which prevents the startup race. But app-level retry handles cases where Postgres restarts mid-run or the init check races a slow auth. Both layers together are the standard production pattern.

Keep learning

Fix the Nginx 502 Bad GatewayDevOps projectRecover a Crashed Linux ServiceDevOps projectCorrect a Postgres Port MismatchDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →