Docker and Kubernetes course menu
Docker Compose with PostgreSQL: A Web App and Database Example
Run a Python API with PostgreSQL using Docker Compose: environment files, named volumes, health checks, depends_on, seeding data and using Compose secrets.
On this page
This is the setup that nearly every web project needs: an app, a database, and data that survives restarts.
The files#
mkdir shop
cd shop
cat > .env <<'EOF'
POSTGRES_USER=shop
POSTGRES_PASSWORD=devpass
POSTGRES_DB=shop
EOF
mkdir db
cat > db/init.sql <<'EOF'
CREATE TABLE products (id serial PRIMARY KEY, name text NOT NULL, price int NOT NULL);
INSERT INTO products (name, price) VALUES ('Tea', 20), ('Coffee', 35);
EOF
cat > app.py <<'EOF'
import os
import psycopg
from flask import Flask, jsonify
app = Flask(__name__)
@app.get("/products")
def products():
with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
rows = conn.execute("SELECT name, price FROM products ORDER BY id").fetchall()
return jsonify([{"name": n, "price": p} for n, p in rows])
EOF
cat > requirements.txt <<'EOF'
flask==3.1.*
gunicorn==23.*
psycopg[binary]==3.2.*
EOF
cat > Dockerfile <<'EOF'
FROM python:3.13-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER nobody
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
EOF
cat > .dockerignore <<'EOF'
.env
db
EOFcompose.yaml#
cat > compose.yaml <<'EOF'
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:17-alpine
env_file: .env
volumes:
- db-data:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 2s
timeout: 3s
retries: 15
volumes:
db-data:
EOFThe important parts:
@db:5432: the app finds the database by its service name.${POSTGRES_USER}: Compose reads a.envfile next tocompose.yaml, and fills these in.$$passes a literal$to the container.healthcheck: this tests whether PostgreSQL is really ready.depends_onwithservice_healthy: the API waits for that health check. Plaindepends_onwaits only for the container to start, which is too early for a database./docker-entrypoint-initdb.d: SQL files here run once, when the data folder is empty.- No
portsondb: the database is not exposed to the outside.
Run it#
docker compose up -d --build --wait --quiet-pull > /dev/null 2>&1
sleep 3
docker compose ps --format '{{.Service}} {{.State}} {{.Health}}'
curl -s http://localhost:8000/productsapi running
db running healthy
[{"name":"Tea","price":20},{"name":"Coffee","price":35}]Work with the database#
docker compose exec db psql -U shop -d shop -tAc "INSERT INTO products (name, price) VALUES ('Dosa', 60)"
docker compose exec db psql -U shop -d shop -tAc "SELECT count(*) FROM products"INSERT 0 1
3Data survives#
down removes the containers and network, but keeps named volumes:
docker compose down > /dev/null 2>&1
docker compose up -d --wait > /dev/null 2>&1
sleep 3
curl -s http://localhost:8000/products[{"name":"Tea","price":20},{"name":"Coffee","price":35},{"name":"Dosa","price":60}]The new row is still there. down -v deletes the volume too, which gives you a fresh database, and runs init.sql again.
Compose secrets#
A safer way to pass the password is with a file:
services:
db:
image: postgres:17-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txtInside the container, the secret appears as the file /run/secrets/db_password.
Back up the database#
docker compose exec -T db pg_dump -U shop shop > backup.sql
grep -c 'COPY public.products' backup.sql
docker compose down -v > /dev/null 2>&11-T turns off the terminal, which is needed when redirecting output.
Common mistakes#
Key takeaway#
Next: Compose for Development.
Finished this lesson?
Related lessons
Recommended Docker and Kubernetes resources
- Docker Deep Dive by Nigel Poulton
A practical guide to how Docker works, for readers who have finished the basics.
Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.