Quick Start
What you will build mentally
A four-service application model: browser-facing frontend, backend API, PostgreSQL database and internal AI service. You will learn service discovery, startup dependencies, health checks, volumes and configuration boundaries.
Start from Basics
Real applications rarely run as one container. A frontend may call a backend, the backend may use a database and an AI service may handle model-specific work. Starting each container with a long docker run command is difficult to reproduce and review.
Docker Compose lets you declare the application in compose.yaml. The file describes services, images or build contexts, networks, volumes, ports, health checks and runtime configuration. docker compose up reconciles that definition into running containers.
Core Explanation
Connect a small application team
Select a service to see whom it calls. Inside Compose, service names act like contact names.
The browser-facing screen sends work to the backend service.
$ docker compose up --buildClick execute — no real system changes are made
The created object will appear here
Architecture / Flow Diagram
01Browser02↓ localhost:300003Frontend container04↓ http://backend:800005Backend container ─────→ AI service:900006↓ postgres:543207Database container08↓09Named volume
Only the frontend must be public in this simple architecture. The backend can be published during local development, but internal services can communicate on the Compose network by service name.
Types / Components
Read a practical compose.yaml
01services:02frontend:03build: ./frontend04ports:05- "3000:3000"06environment:07API_BASE_URL: http://backend:800008depends_on:09backend:10condition: service_healthy1112backend:13build: ./backend14environment:15DATABASE_URL: postgresql://app:app@postgres:5432/app16AI_SERVICE_URL: http://ai-service:900017depends_on:18postgres:19condition: service_healthy20ai-service:21condition: service_started22healthcheck:23test: ["CMD", "curl", "-f", "http://localhost:8000/health"]24interval: 10s25timeout: 3s26retries: 52728postgres:29image: postgres:1730environment:31POSTGRES_USER: app32POSTGRES_PASSWORD: app33POSTGRES_DB: app34volumes:35- postgres-data:/var/lib/postgresql/data36healthcheck:37test: ["CMD-SHELL", "pg_isready -U app"]38interval: 10s39timeout: 5s40retries: 54142ai-service:43build: ./ai-service44environment:45MODEL_NAME: local-demo-model4647volumes:48postgres-data:
This is a learning configuration, not a production-ready security policy. Never use the example database password in a real environment.
The most important networking rule
Inside the frontend container, localhost means the frontend container itself. It does not mean the backend container. Compose service discovery lets the frontend reach http://backend:8000 and the backend reach postgres:5432.
This single idea solves many beginner connection failures.
Simple Tanglish — localhost rule
Container-க்குள் localhost என்றால் அந்த same container. Backend database-ஐ call பண்ணும்போது localhost:5432 use பண்ணக்கூடாது. Compose service name use பண்ணணும்: postgres:5432.
depends_on is not business readiness
Starting a database process does not mean it is ready to accept queries. A health check can give Compose a stronger readiness signal, and depends_on can wait for that condition. Your application should still handle temporary unavailability with bounded retries and clear errors because services can fail after startup.
Operate the stack
01docker compose up --build02docker compose ps03docker compose logs -f backend04docker compose exec backend sh05docker compose down
Use docker compose down -v only when you intentionally want to remove named volumes declared by the project. That can delete database data and should not be a casual cleanup command.
Docker Run vs Docker Compose
| Decision | Separate docker run commands | Docker Compose |
|---|---|---|
| Application definition | Spread across commands | Recorded in compose.yaml |
| Service communication | Network and names configured manually | Service names available on the project network |
| Team repeatability | Easy to miss one option | One shared definition |
| Best fit | One quick container | Connected local application stack |
Real-World Examples
- Before Docker
- The API tries localhost and cannot find the database container.
- Team action
- Use the Compose service address postgres:5432.
- After Docker
- The API reaches the database by service name.
- Before Docker
- Four separate docker run commands are easy to mistype.
- Team action
- Record the services in compose.yaml and run docker compose up.
- After Docker
- The application team starts from one definition.
- Before Docker
- The database container is recreated during development.
- Team action
- Attach a named volume in the Compose file.
- After Docker
- The replacement service keeps the selected data.
Imagine Pannunga
One customer experience, several specialised stations
A restaurant is one customer experience but contains a host desk, kitchen, pantry and billing station. Each has a separate responsibility and communicates through known handoffs. A manager does not combine every role into one person merely to simplify the floor plan.
Compose is the operating plan: which stations exist, how they are reached, what supplies persist and which station must be ready first. The analogy stops at runtime resilience—software services still need timeouts, retries, monitoring and failure isolation.
Use Cases
“How do I start the full app locally?”
Run docker compose up“Can I reproduce the connected stack?”
Use the committed Compose definition“Which services are public or internal?”
Review ports, networks and secrets“Which service is unhealthy?”
Inspect service state and focused logsKey Takeaways
- Compose records a multi-container application in one file.
- Each service should have one clear responsibility.
- Service names provide addresses inside the Compose network.
localhostalways refers to the current container.- Publish only services that need host access.
- A started service may not yet be ready.
- Named volumes keep selected state outside one container.
- Focused logs identify which service failed first.
Final Thought + Next Path
Think of Compose as the operating plan for a small application team: it names each service, records how they connect and starts them from one definition. In Byte 05, you will take the packaged application toward deployment with versioned images, runtime secrets, health checks and rollback.
FAQ + Knowledge Check
Choose an answer, inspect the explanation and explain the idea in your own words.
Why cannot the backend use localhost for PostgreSQL? Because localhost points back to the backend container. Use the service name postgres.
Does depends_on prove that the database is ready? No. It helps with order; readiness needs a health signal and resilient application behaviour.
Knowledge check: Which command starts the defined stack? docker compose up. Which command shows service states? docker compose ps.
A disciplined debugging sequence
- Run
docker compose configto inspect the resolved configuration. - Run
docker compose psto check service and health states. - Read logs for the service receiving the failed request.
- Confirm it uses service names, not
localhost, for container-to-container calls. - Test connectivity from the calling container.
- Check configuration, credentials and database migrations.
Do not restart every service immediately; restarts can hide the first useful failure evidence.
Common mistakes
Common mistakes
- Using
localhostto call another container. - Publishing every internal service port to the host.
- Assuming startup order guarantees application readiness.
- Storing production passwords directly in
compose.yaml. - Running stateful databases without a volume and backup strategy.
- Treating local Compose as a complete production orchestration design.
Interview answer
How do services communicate in Docker Compose?
Strong answer: Compose creates a default network for the application, and services can reach one another using service names as DNS names. Internal traffic uses the container port, while published ports are for access through the host. I use health checks and application-level retry behaviour instead of assuming a started container is ready.