# Connecting Python and Postgres Using Docker Compose

*Hello, I'm Ganesh. I'm working on* [*FreeDevTools online*](https://hexmos.com/freedevtools)*,* [*currently building*](https://hexmos.com/freedevtools) *one place for all dev tools, cheat codes, and TLDRs* — a free, open-source hub where developers can quickly find and use tools without any hassle of searching all over the internet.

Ever had a Python script that needed a database, but you didn't want to go through the hassle of installing and configuring Postgres on your local machine? This is a perfect job for Docker, and more specifically, **Docker Compose**.

In this post, we'll walk through the entire process of setting up a multi-container Docker environment. We'll build two services:

1. A **Postgres** database server.
    
2. A **Python Flask** web app that connects to the database.
    

We'll not only build it, but we'll also learn the "magic" of Docker networking, how to make services wait for each other, and how to debug when things (inevitably) go wrong.

## The Four Files We Need

Our entire project will live in one folder and consist of just four files:

1. `docker-compose.yml`: The "conductor" that tells Docker Compose how to build and run our services.
    
2. `Dockerfile`: The "recipe" for building our custom Python application image.
    
3. [`app.py`](http://app.py): Our actual Python Flask application script.
    
4. `requirements.txt`: The list of Python libraries our app needs.
    

Let's build them one by one.

## The Application

First, let's define our Python application.

### The Dependencies: `requirements.txt`

Our app needs two libraries: **Flask** to be a web server and **psycopg2** to talk to Postgres.

```bash
psycopg2-binary
flask
```

### The App: [`app.py`](http://app.py)

This will be a simple Flask app. When you visit the homepage (`/`), it will try to connect to our Postgres database. It will return a success message if it works or an error if it doesn't.

Notice how we get the database details (like the host and password) from **environment variables**. This is a best practice and crucial for Docker.

```bash
from flask import Flask
import psycopg2
import os

app = Flask(__name__)

# Get database connection details from environment variables
db_host = os.environ.get('DB_HOST')
db_name = os.environ.get('DB_NAME')
db_user = os.environ.get('DB_USER')
db_pass = os.environ.get('DB_PASSWORD')

@app.route('/')
def check_db():
    try:
        # Try to connect to the database
        conn = psycopg2.connect(
            host=db_host,
            dbname=db_name,
            user=db_user,
            password=db_pass
        )
        conn.close()
        # If connection is successful
        return "Success! Connected to the PostgreSQL database."
    except psycopg2.OperationalError as e:
        # If connection fails
        return f"Connection failed: {e}", 500

if __name__ == '__main__':
    # 'host=0.0.0.0' makes it accessible outside the container
    app.run(host='0.0.0.0', port=5000)
```

### The Recipe: `Dockerfile`

Now, how do we package this app into a Docker image? We use a `Dockerfile`.

```bash
# 1. Start from the Python 3.10-slim base image
FROM python:3.10-slim

# 2. Set the working directory inside the container
WORKDIR /app

# 3. Copy our project files (everything in the folder) into the /app directory
COPY . .

# 4. Install our Python libraries
RUN pip install -r requirements.txt

# 5. Set the default command to run when the container starts
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]
```

This file is a simple set of instructions: start with Python, create an `/app` folder, copy our code in, install dependencies, and finally, define the command to run our Flask app.

## Part 2: docker-compose

This file is where the magic happens. It defines our *services* and connects them. We'll have two services: `db` (for Postgres) and `app` (for our Python code).

Here is the complete file. We'll break it down right after.

```bash
version: '3.8'

services:
  # 1. The Postgres Database Service
  db:
    image: postgres:17-alpine
    environment:
      - POSTGRES_PASSWORD=mysecretpassword
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
    volumes:
      - db-data:/var/lib/postgresql/data
    ports:
      - "5432:5432" # Optional: Lets us connect from our PC

  # 2. The Python Application Service
  app:
    build: .
    ports:
      - "4000:5000" # Host port 4000 -> Container port 5000
    environment:
      - DB_HOST=db
      - DB_PASSWORD=mysecretpassword
      - DB_USER=postgres
      - DB_NAME=postgres
    depends_on:
      - db

# This defines the named volume we used above
volumes:
  db-data:
```

### Key Concepts in This File

* `services: db:`: Defines our database.
    
    * `image: postgres:17-alpine`: We don't build this. We just pull the official lightweight (alpine) Postgres image from Docker Hub.
        
    * `environment`: We pass in the *required* variables that the `postgres` image needs to initialize itself.
        
    * `volumes: - db-data:/...`: This is critical. It maps a "named volume" called `db-data` to the folder *inside* the container where Postgres stores its data. This means if you stop and restart your container, your data will still be there.
        
* `services: app:`: Defines our Python app.
    
    * `build: .`: This is the most important part. Instead of an `image`, we say `build`. The `.` tells Docker Compose, "Look for a `Dockerfile` in the current directory and build it."
        
    * `ports: - "4000:5000"`: This maps port **4000** on our host machine (your computer) to port **5000** inside the container (which is where Flask is running).
        
    * `environment`: This is how we pass the connection details to our [`app.py`](http://app.py) script.
        
    * `depends_on: - db`: This tells Docker Compose, "Do not start the `app` container until the `db` container is running." This prevents our app from crashing on startup because the database isn't ready.
        

### The Most Important Part: Networking

Look at the `environment` section for our `app` service:

`DB_HOST=db`

You might be asking, "What is `db`? Is that a real hostname?"

**Yes!** Inside the private network that Docker Compose creates, every service is reachable by its **service name**. Because we named our Postgres service `db`, our `app` container can find it simply by connecting to the host `db`. No IP addresses needed. This is the core magic of Docker Compose networking.

## Part 3: The Launch and The Debug

We have all four files. Let's start it up. In your terminal, in the same folder, run:

```bash
docker compose up
```

This will build your `app` image, pull the `postgres` image, and start both containers.

But wait! What if it doesn't work? Let's walk through a real debugging flow that often happens.

**Problem:** You run `docker ps` and you only see the `postgres` container. Your `app` container isn't running.

This means the app container started and then immediately stopped (crashed).

Step 1: Check all containers

The docker ps command only shows running containers. To see stopped ones, use the -a flag.

```bash
docker ps -a
```

You'll probably see your app container with a status like `Exited (2) ...`. This confirms it crashed.

```bash
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS                     PORTS     NAMES
e8fe79a88d90   pg-python-docker-app   "flask run --host=0.…"   2 minutes ago   Exited (2) 2 minutes ago             pg-python-docker-app-1
ef590f462bb8   postgres:17-alpine     "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes               ...       pg-python-docker-db-1
```

Step 2: Read the logs

To determine the cause of the crash, we reviewed its logs using the container's name or ID.

Bash

```bash
docker logs pg-python-docker-app-1
```

The Culprit: In our example, we'd see this error:

Error: Could not locate a Flask application. Use the 'flask --app' option, 'FLASK\_APP' environment variable, or a '[wsgi.py](http://wsgi.py)' or '[app.py](http://app.py)' file...

Ah! The `flask run` Command defaults to looking for a file named [`app.py`](http://app.py). If we had named our file [`main.py`](http://main.py), it would have failed. (We named ours [`app.py`](http://app.py) from the start to avoid this, but it's the most common first-time error!)

Step 3: Fix and Rebuild

Let's say you named it [main.py](http://main.py) and then renamed it to [app.py](http://app.py). If you run docker compose up again, it still won't work.

Why? Because Docker has **cached** your old image. It doesn't know you changed the files.

We need to tell Docker Compose to **force a rebuild** of the image.

```bash
docker compose up --build
```

The `--build` flag tells Compose to re-run the `Dockerfile` steps, copying in our new [`app.py`](http://app.py) file. This time, when the container starts, `flask run` will find [`app.py`](http://app.py) and launch successfully.

## The Final Check

After running `docker compose up --build`, run `docker ps` again. You should now see **both** containers in the `Up` state.

```bash
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                                                 NAMES
f4a9b8e3a2b1   pg-python-docker-app   "flask run --host=0.…"   30 seconds ago   Up 29 seconds   0.0.0.0:4000->5000/tcp                               pg-python-docker-app-1
c1b2a3d4e5f6   postgres:17-alpine     "docker-entrypoint.s…"   32 seconds ago   Up 31 seconds   0.0.0.0:5432->5432/tcp                               pg-python-docker-db-1
```

Now, open your web browser and go to [`http://localhost:4000`](http://localhost:4000).

You should see our success message: "Success! Connected to the PostgreSQL database."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761763957471/57f44b9f-b394-4e66-a460-04385265994a.png align="center")

  

You've just built a fully-functional, multi-container application, complete with a persistent database and a web frontend, all orchestrated by one command. Happy Dockering!

[![FreeDevTools](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5e57lh7rtwwwe2d694n.png align="left")](https://hexmos.com/freedevtools/)

I’ve been building for **FreeDevTools**.

A collection of UI/UX-focused tools crafted to simplify workflows, save time, and reduce friction in searching tools/materials.

Any feedback or contributions are welcome!

It’s online, open-source, and ready for anyone to use.

👉 Check it out: [FreeDevTools](https://hexmos.com/freedevtools)  
⭐ Star it on GitHub: [freedevtools](https://github.com/HexmosTech/FreeDevTools)
