Run a two-tier app with Compose, safely
Task
Bring up an application and its database with Compose, wiring them together correctly: named volume for the data, a health-gated dependency, a loopback-bound port, and a secret that is not an environment variable. Then prove the down/up cycle keeps the data and that down -v destroys it.
Steps
- Write a compose file with a
dbservice using a named volume for its data directory, and anappservice that connects to it. - Bind the app's published port to loopback --
"127.0.0.1:8080:8080"-- not to every interface. Confirm withssafterup. - Wire
depends_onwithcondition: service_healthyand give the db ahealthcheck. Prove the app waits for the db to be ready, not merely started, by watching the startup order indocker compose logs. - Supply the db password as a Compose secret backed by a file, not as an environment variable. Confirm it is not visible in
podman inspector the host process list. - Bring it up with
up -d, insert data, thendown(without -v) andupagain. Confirm the data survived, because named volumes outlivedown. - Now run
down -vandup, and confirm the data is gone. State the difference between the two commands in one sentence. - Read aggregated logs with
docker compose logs -f appand identify a startup line that proves the db-readiness gate worked.
Verify
cd ~/lab-compose
ss -tlnp | grep ':8080' # 127.0.0.1 only
docker compose exec db psql -U app -tAc 'select count(*) from items' # survives down/up
docker compose config | grep -A2 secrets # secret, not env var
docker compose down -v && docker compose up -d
docker compose exec db psql -U app -tAc 'select count(*) from items' # 0 after -v
The two counts are the lesson: data survives down and does not survive down -v. The loopback bind in the first check is the exposure control -- a database published on 0.0.0.0 is reachable from anything that can reach the host.
Notes
depends_on alone means "start after", not "wait until ready". Without the service_healthy condition an app that connects to its database on startup will still fail the race, intermittently, in a way that looks like an application bug rather than a compose configuration gap.