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 --postgres:16, withPOSTGRES_USER: app, a named volume at/var/lib/postgresql/data-- and anappservice that connects to it. The data you insert later goes in a table calleditems. - 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 --wait # --wait: until healthy
docker compose exec db psql -U app -tAc 'select count(*) from items' 2>&1 | grep -q 'does not exist' && echo "gone with the volume"
The two results are the lesson: the count survives down, and after down -v there is not even an items table to count -- the whole database was in the volume. (--wait holds until the healthcheck passes; without it the query can race a database that is still starting.) 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.
This is an independent study companion for CompTIA Linux+ and is not produced by or endorsed by CompTIA.