Docker Compose and Traefik

How make up turns one YAML file into a running exchange platform: containers, networks, volumes, health checks and start order, then a reverse proxy that gives every service a friendly name on a single port.

1. The problem

Prayog needs PostgreSQL, Kafka, Redis, Keycloak and a proxy, each at a specific version and configuration, started in the right order, on every developer's machine and in CI. Installing them by hand gives "works on my machine" bugs. Compose describes the whole stack in one file, so everyone runs exactly the same thing.

2. A business example: the shopping mall

3. Images and containers

4. Compose basics

ConceptWhat it doesPrayog example
ServiceA container definition: image, command, environmentkafka, keycloak
NetworkCompose puts all services on one private network with DNS: each service is reachable by its nameKeycloak connects to postgres:5432; containers use kafka:9092
Port mappingPublishes a container port on the host127.0.0.1:5432:5432: only your own machine can connect
Named volumeStorage that outlives the containerpostgres-data, kafka-data, redis-data
Bind mountA host folder inside the containerThe realm file at /opt/keycloak/data/import, read-only
Environment and .env${VAR} is filled from .env; ${VAR:?msg} refuses to start if it's missingEvery password and client secret
ProfilesStart only groups of servicesinfra now, app from S10, obs in S21
One-shot jobA container that does one task and exits 0kafka-init creates the topic

5. Started vs. ready

A container "starts" when its process starts. PostgreSQL then needs a few seconds before it accepts connections; Keycloak needs about 20. If Keycloak starts first and tries to connect, it fails and exits.

ToolWaits for
depends_on: [postgres]Postgres started. Not enough.
depends_on: postgres: condition: service_healthyPostgres's health check passing
condition: service_completed_successfullyA one-shot job exiting 0
docker compose up --waitEvery service healthy (or running, if it has no check) before the command returns

A health check should ask the question clients care about: pg_isready rather than "is port 5432 open", a real Kafka API call, Keycloak's /health/ready. Prayog's Keycloak check opens a TCP connection with bash's built-in /dev/tcp, because the image has no curl.

6. Try it: start order

Each service takes a while to start and then to become ready. Compare plain depends_on with health conditions.

startingreadyfailed: dependency not ready

7. Reverse proxy and Traefik

A reverse proxy receives every request on one port and forwards it to the right service. Benefits: one address to remember, friendly host names, one place for TLS, redirects and access logs, and services that never expose ports themselves.

Traefik's model:

PieceMeaningPrayog
EntrypointA port Traefik listens onweb = :80
RouterA rule that matches requestsHost(`auth.prayog.localhost`)
ServiceWhere matched requests goKeycloak container port 8080
MiddlewareChanges on the way (auth, headers, rate limits)None yet
ProviderWhere Traefik learns routesDocker: labels on each container, read through the Docker socket

With exposedbydefault=false, a container is routed only if it says traefik.enable=true, so nothing is published by accident. Traefik forwards the original host and client address in X-Forwarded-* headers; Keycloak trusts them (KC_PROXY_HEADERS=xforwarded) to build correct URLs.

Why .localhost? RFC 6761 reserves localhost and every name under it for the local machine. Browsers, curl and (on this Mac) Python resolve anything.prayog.localhost to 127.0.0.1 with no DNS setup. Some tools don't, hence the /etc/hosts line in the README.

8. Try it: routing

Type a host name. The table of routers is Prayog's real one.

9. Security edges

10. In Prayog

CommandDoes
make envCreates .env with random secrets (once)
make upcompose --profile infra up -d --wait: returns when all is healthy
make smoke20 end-to-end checks; also run in CI
make ps, make logs SERVICE=kafkaStatus and logs
make down / make resetStop, keeping data / stop and delete data (needed after realm or init changes)

Measured on an 8 GB M1: about 2.5 minutes for the first start (image pulls), 22 seconds afterwards, about 1.1 GB of memory in total.

11. Questions

How does this map to Kubernetes?

Services become Deployments (or StatefulSets for databases and Kafka), health checks become readiness and liveness probes, Traefik's labels become Ingress or Gateway API routes, named volumes become PersistentVolumeClaims, and .env becomes Secrets. The ideas carry over directly.

Why Traefik and not nginx?

Both work. Traefik discovers routes from container labels and updates them live, so adding a service needs no proxy config file and no reload. nginx needs a config entry per service.

Why not run Keycloak without a database for local use?

Its development mode can use an embedded file database, but then the local setup differs from a real one. Running it on PostgreSQL costs about 50 MB and tests the real path.