Course outline · 0% complete

0/29 lessons0%

Course overview →

App plus database, wired properly

lesson 5-2 · ~12 min · 15/29

Configuration through the environment

Passing DATABASE_URL as an environment variable instead of hardcoding it exists for one reason: the same image from lesson 3-1 must run everywhere, on your laptop, on the CI server, and in production. CI is short for continuous integration, an automated machine that tests every push, and unit 7 is devoted to it.

The code stays identical and only the environment changes per machine. This convention is strong enough to have a name, the twelve-factor config rule.

The database side of the pair needs its own env vars. The official postgres image reads these on first boot:

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: shop
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: shop
    volumes:
      - dbdata:/var/lib/postgresql/data
VariableMust match
POSTGRES_USERthe user in DATABASE_URL
POSTGRES_PASSWORDthe password in DATABASE_URL
POSTGRES_DBthe database name in DATABASE_URL

User, password, and database name here must match what the app's DATABASE_URL claims, or connections fail. When a compose stack is broken, comparing these two blocks is the first debugging move.

Assembling the URL from its pieces

A connection URL is built from exactly the pieces sitting in the env blocks. Holding each in a variable makes the shape obvious.

user="shop"
password="secret"
host="db"
port="5432"
dbname="shop"
echo "postgres://$user:$password@$host:$port/$dbname"

Output

postgres://shop:secret@db:5432/shop
URL positionComes from
before the colonPOSTGRES_USER
between colon and @POSTGRES_PASSWORD
after @the compose service name
after the last slashPOSTGRES_DB

Every piece is plain variable expansion inside one echo string, which works because double quotes allow expansion while keeping the whole thing a single argument. Writing it out this way is a useful habit when a URL is rejected, since it forces each component to be checked separately.

What depends_on does and does not do

    depends_on:
      - db

This controls start order only, so compose launches db before app. It does not wait for Postgres to become ready to accept connections, which takes a few seconds after the container starts.

GuaranteeProvided by plain depends_on
db container starts firstyes
Postgres accepts connectionsno

So the app can still boot, try to connect, and fail while the database is warming up. There are two real fixes.

  1. The app retries its database connection on startup, which is good practice regardless.
  2. A healthcheck on the db service, plus depends_on with condition: service_healthy, so compose waits for genuine readiness.

Healthchecks get their own treatment in lesson 9-2. The trap to remember is that started is not ready.

A start-order guarantee that is not a readiness guarantee

With plain depends_on: [db], compose starts db first and the app can still crash with "connection refused" on a slow machine, because depends_on starts the db container without waiting for Postgres inside it to be ready for connections.

depends_on sequences container startup, not application readiness, and Postgres needs a few seconds after its container starts.

FixNature
retry the connection in the appapplication-level, always worth having
healthcheck plus condition: service_healthycompose-level, waits for real readiness

The retry loop is the more robust of the two, because it also survives a database restart in the middle of the day rather than only at boot. The healthcheck route is covered in lesson 9-2.

A password mismatch between blocks

With POSTGRES_PASSWORD set to secret and the app's DATABASE_URL reading postgres://shop:hunter2@db:5432/shop, the connection fails.

The database accepts the password from its own environment, which is secret, and the app presents hunter2, so Postgres rejects the authentication.

SidePassword
db service environmentsecret
app connection URLhunter2

Mismatched env blocks like this are the most common broken-compose-stack bug, which makes diffing them the first thing to do. The error message says authentication failed rather than anything about compose, so the cause is easy to look past.