Day 149 — docker-compose for a local stack¶
Month 6 · Week 2 · ⬅ Day 148 · Day 150 ➡ · Journal index
🎯 Learning Objective¶
Define a reproducible local stack (Go app + Postgres + Redis) with Docker Compose, wired together by service-name DNS, depends-on/healthchecks, and env-file config.
📚 Topics¶
compose.yaml: services, networks, volumes,depends_onwithcondition- Service discovery by service name on the default bridge network
healthcheck,env_file, and the*_FILEsecret convention
📖 Reading / Sources¶
📝 Notes¶
- A
compose.yamldeclares a stack of services;docker compose upbuilds and starts them on a shared user-defined network → [[docker-compose]]. - Service discovery is DNS by service name: the app connects to host
db, notlocalhostor an IP. Compose runs an embedded DNS resolver sodb:5432resolves to the Postgres container. depends_onalone only orders start, not readiness. A container can be "started" before Postgres accepts connections. Addcondition: service_healthy+ ahealthcheckso the app waits for the DB to be truly ready → [[healthcheck]].- App config still comes from the environment ([[12-factor]]); Compose injects it via
environment:or anenv_file:. Secrets use the*_FILEpattern so values come from mounted files, not inline env → [[day-152]]. - Named volumes persist DB data across
up/down; without one,docker compose downwipes the database.down -vremoves volumes too. localhostinside a container is the container itself. To reach the host machine usehost.docker.internal; to reach a sibling service use its service name.- Compose is a local/dev tool. Production orchestration (Kubernetes, ECS) reuses the same image but not this file.
💻 Code Examples¶
# compose.yaml — local app + datastores
services:
app:
build:
context: .
args:
VERSION: dev
ports:
- "8080:8080" # host:container
environment:
PORT: "8080"
LOG_LEVEL: "debug"
DATABASE_URL: "postgres://app:app@db:5432/app?sslmode=disable"
REDIS_ADDR: "cache:6379"
depends_on:
db:
condition: service_healthy # wait until the DB passes its healthcheck
cache:
condition: service_started
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 2s
timeout: 3s
retries: 10
volumes:
- dbdata:/var/lib/postgresql/data
cache:
image: redis:7
volumes:
dbdata:
The app reads DATABASE_URL/REDIS_ADDR exactly like the stdlib config loader below.
Full code:
examples/month-06/config/main.go· Run:go run ./examples/month-06/config
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| Parse the env Compose injects, fail-fast on bad values | ✅ | exercises/month-06/week-2/envconfig |
🐛 Mistakes Made¶
- App raced the DB: it crashed on the first connect because
depends_ononly waited for start. Added ahealthcheck+condition: service_healthy. - Pointed the app at
localhost:5432; got connection refused. Inside a container that's the container itself — changed to the service namedb.
❓ Open Questions¶
- Should the app still implement retry/backoff on connect even with healthchecks? (Yes — healthchecks reduce, not eliminate, startup races and mid-life blips.)
🧠 Active Recall (answer without looking)¶
- Q: What hostname does the app use to reach the Postgres service, and why not
localhost?A
The service name db. Compose's embedded DNS resolves service names on the shared network. localhost inside a container refers to that container, not to sibling services.
2. Q: Why isn't depends_on: [db] enough to avoid connection errors at startup? A
depends_on only orders container start, not application readiness. Postgres may not yet accept connections. Use a healthcheck plus condition: service_healthy.
🪶 Feynman Reflection¶
Compose is a recipe that boots several containers as one stack and gives them a private network where they find each other by name. depends_on says "start B before A"; a healthcheck says "and don't tell A it's ready until B actually answers."
🕳️ Knowledge Gaps¶
- Compose
profilesand override files (compose.override.yaml) for dev-vs-test variants — skim later.
✅ Summary¶
I can stand up a multi-service local stack with Compose, connect services by DNS name, and gate startup on real readiness via healthchecks.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 150: harden the runtime image — non-root, distroless, read-only filesystem.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: docs(journal): docker-compose local stack (day 149)