Unit 3 - Notes
Unit 3: Microservices with Docker Compose
1. Microservices Architecture
1.1 The Need for Microservices
In modern software development, applications must be highly scalable, available, and capable of rapid iteration. Traditional application architectures often struggle to meet these demands as the codebase grows. The need for microservices arises from:
- Complexity Management: Large applications become too complex for a single developer or team to understand fully.
- Release Bottlenecks: A minor change in one module requires rebuilding and deploying the entire application.
- Technology Lock-in: It is difficult to adopt new technologies or frameworks in a traditional architecture without rewriting the whole system.
- Resource Utilization: Scaling a traditional application means scaling the entire system, even if only one specific function requires more resources.
1.2 Monolithic vs. Microservices Architecture
| Feature | Monolithic Architecture | Microservices Architecture |
|---|---|---|
| Structure | A single, unified, indivisible unit. All logic is in one codebase. | A collection of small, independent, loosely coupled services. |
| Coupling | Tightly coupled. Components depend heavily on one another. | Loosely coupled. Services communicate via APIs (e.g., REST, gRPC). |
| Scaling | Scales by duplicating the entire application across multiple servers (vertical/horizontal). | Scales independently. Only services with high load are scaled. |
| Deployment | Entire application must be deployed at once. Slower CI/CD pipelines. | Independent deployments. A single service can be updated seamlessly. |
| Tech Stack | Constrained to a single technology stack/language. | Polyglot. Each service can use the best language/DB for its specific task. |
| Failure Impact | A bug in one module (e.g., memory leak) can crash the whole system. | Failures are isolated. If one service fails, the rest of the application remains functional. |
1.3 Advantages of Microservices
- Scalability: Services can be scaled independently on demand. For an e-commerce platform, the "Checkout" service can be scaled heavily during a sale without scaling the "User Profile" service.
- Isolation (Fault Tolerance): Process isolation ensures that a failure in one service does not cascade into a complete system failure. Security boundaries are also tighter.
- Agility (Rapid Iteration): Smaller, focused teams can own a service from end to end. They can develop, test, and deploy independently, greatly reducing the time-to-market for new features.
1.4 API Gateway
An API Gateway acts as a reverse proxy to accept all application programming interface (API) calls, aggregate the various services required to fulfill them, and return the appropriate result.
- Role in Microservices: Clients do not need to know the IP address or routing logic of dozens of microservices. They talk to the API Gateway.
- Key Functions:
- Request Routing: Directs incoming requests to the appropriate backend microservice.
- Authentication & Authorization: Validates user credentials centrally before requests hit backend services.
- Rate Limiting & Throttling: Prevents abuse and protects backend services from being overwhelmed.
- Load Balancing: Distributes requests evenly across multiple instances of a service.
- Response Aggregation: Combines responses from multiple microservices into a single payload for the client.
2. Docker Compose Fundamentals
Docker Compose is a tool for defining and running multi-container Docker applications. It uses a YAML file to configure application services, networks, and volumes, allowing developers to spin up complex architectures with a single command (docker-compose up).
2.1 YAML Structure and Writing docker-compose.yml
YAML (YAML Ain't Markup Language) is a human-readable data serialization standard. In Docker Compose, YAML relies on indentation (using spaces, not tabs) to denote structure.
2.2 Core Components
version: Historically used to specify the Compose file format version (e.g.,version: '3.8'). Note: In newer Docker Compose V2 specifications, the version tag is deprecated and often omitted, but still widely found in legacy files.services: The core of the file. It defines the individual containers that make up your application (e.g.,web,database,cache).volumes: Used for persistent data storage. Defines named volumes that can be shared across containers or persist when containers are destroyed.networks: Defines custom networks to control communication between containers. Containers in the same network can communicate using their service names as hostnames.
2.3 Environment Variables, Secrets, and Configs
- Environment Variables: Passed to containers using the
environmentkey or via an external.envfile usingenv_file. Useful for passing dynamic configuration like database URLs. - Secrets: Sensitive data (passwords, API keys) should not be hardcoded. Compose allows defining secrets that are securely mounted into the container at
/run/secrets/<secret_name>. - Configs: Similar to secrets but for non-sensitive configuration files (e.g., Nginx config files). Mounted directly into the container filesystem.
2.4 Build vs. Image Fields
image: Tells Compose to pull a pre-built image from a container registry (like Docker Hub). Example:image: mysql:8.0.build: Tells Compose to build an image from a localDockerfile. You specify the context (directory) and optionally the Dockerfile name.
YAMLbuild: context: ./backend dockerfile: Dockerfile.dev- Note: You can use both together. If you specify both, Compose will build the image from the Dockerfile and tag it with the name specified in
image.
2.5 Service Dependency Ordering (depends_on)
By default, Docker Compose starts all services simultaneously. depends_on controls the startup and shutdown order.
- Standard usage: Starts service B only after service A starts.
- Condition based: (e.g.,
condition: service_healthy) Ensures service B waits until service A is fully operational (requires ahealthcheckdefined in Service A).
3. Use Case Deployments
3.1 Generalized Multi-Container App (Database + Backend + Frontend)
A typical modern web application requires a frontend (React/Angular), a backend (Node/Python/Java), and a database (MySQL/Postgres/MongoDB). Docker Compose creates an isolated network where the Backend can securely talk to the DB, and the Frontend interacts with the Backend, often exposed to the host machine.
3.2 WordPress + MySQL Deployment
This deployment uses pre-built official images from Docker Hub.
services:
db:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
volumes:
- db_data:/var/lib/mysql
wordpress:
depends_on:
- db
image: wordpress:latest
restart: always
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306 # Resolves to the 'db' service above
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
volumes:
db_data:
wp_data:
3.3 Node.js + MongoDB Deployment
This example builds a custom Node.js backend while utilizing an official MongoDB image.
services:
backend:
build: ./api-server # Path containing Node.js Dockerfile
ports:
- "3000:3000"
environment:
- MONGO_URI=mongodb://database:27017/myapp
- NODE_ENV=development
depends_on:
- database
networks:
- mern-network
database:
image: mongo:6.0
volumes:
- mongo-data:/data/db
networks:
- mern-network
volumes:
mongo-data:
networks:
mern-network:
driver: bridge
3.4 Java Spring Boot + PostgreSQL Deployment
This deployment demonstrates depends_on with health checks, ensuring the Spring Boot application does not attempt to connect before PostgreSQL is fully initialized.
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: adminpassword
POSTGRES_DB: springdb
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d springdb"]
interval: 10s
timeout: 5s
retries: 5
springboot-app:
build:
context: ./spring-app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/springdb
SPRING_DATASOURCE_USERNAME: admin
SPRING_DATASOURCE_PASSWORD: adminpassword
depends_on:
postgres:
condition: service_healthy # Waits for DB healthcheck to pass
volumes:
pg_data: