Compose applications
Configure the database service
Supply PostgreSQL configuration without baking environment-specific values into the application image.
The official PostgreSQL image won’t start without a password. And the Notes API needs a connection URL to find the database. Both are configuration, and both go into the container as environment variables at run time, not into the image.
Add the variables
Extend compose.yaml:
services:
api:
environment:
DATABASE_URL: postgres://notes:development-only@database:5432/notes
database:
environment:
POSTGRES_USER: notes
POSTGRES_PASSWORD: development-only
POSTGRES_DB: notes
The three POSTGRES_* variables tell the image which user, password, and database to create on first start. DATABASE_URL puts the same values together for the API: user notes, password development-only, host database (the service name, as we saw), port 5432, database notes.
Start only the database and read its logs:
docker compose up -d database
docker compose logs database
On the first run you’ll see lines about initializing the database, then database system is ready to accept connections. That’s the confirmation.
These variables only work once
Here’s the thing that trips everyone up. The POSTGRES_* variables are read by the image’s init script, and that script runs only when the data directory is empty. On first start the volume is empty, so PostgreSQL creates the user and database.
Now change POSTGRES_PASSWORD in the file and run up again. Nothing happens. The volume already has a database, the init script skips, and the old password stays. The API fails to authenticate, and you stare at the file wondering why it’s ignored.
To change credentials on an existing database, do it in PostgreSQL (ALTER USER notes PASSWORD '...') or through a migration. Or, in development, wipe the volume with docker compose down -v and start fresh. Never do that in production without a tested backup.
Keep real secrets out of Git
development-only is a fine password for a laptop. It’s in a file you commit, and that’s the point: anyone can clone and run. But it must be obvious that it’s local. Never let a development default become the fallback in production.
For real deployments, keep secrets out of the Compose file. Use a .env file listed in .gitignore and reference it with ${POSTGRES_PASSWORD}, or, better, the secret store of your platform.
And be aware that a connection URL leaks easily. It shows up in docker inspect, in ps output, and in any log that prints the environment. Give the database user only the permissions the API needs, and rotate the password without rebuilding the image. That’s the whole reason it’s not in the image.
Try this: add the configuration, start only the database, and read docker compose logs database until you see it accepting connections.
Lesson completed