#Lab: Multi-Container Application with Docker Compose
Build a full-stack application using Docker Compose.
#๐ฏ Objectives
- Create a multi-service application
- Configure service dependencies
- Manage data with volumes
- Set up container networking
#๐ Prerequisites
- Docker and Docker Compose installed
- Basic Docker knowledge
#โฑ๏ธ Duration: 45 minutes
#Task 1: Project Setup (5 min)
bash
mkdir ~/docker-fullstack && cd ~/docker-fullstackCreate directory structure:
bash
mkdir -p backend frontend#Task 2: Create Backend (Node.js API) (10 min)
#backend/package.json
bash
1cat << 'EOF' > backend/package.json
2{
3 "name": "api",
4 "version": "1.0.0",
5 "main": "server.js",
6 "dependencies": {
7 "express": "^4.18.2",
8 "pg": "^8.11.3"
9 }
10}
11EOF#backend/server.js
bash
1cat << 'EOF' > backend/server.js
2const express = require('express');
3const { Pool } = require('pg');
4
5const app = express();
6app.use(express.json());
7
8const pool = new Pool({
9 host: process.env.DB_HOST || 'db',
10 user: process.env.DB_USER || 'postgres',
11 password: process.env.DB_PASSWORD || 'secret',
12 database: process.env.DB_NAME || 'app'
13});
14
15app.get('/health', (req, res) => {
16 res.json({ status: 'healthy', timestamp: new Date() });
17});
18
19app.get('/api/items', async (req, res) => {
20 try {
21 const result = await pool.query('SELECT * FROM items ORDER BY id');
22 res.json(result.rows);
23 } catch (err) {
24 res.status(500).json({ error: err.message });
25 }
26});
27
28app.post('/api/items', async (req, res) => {
29 const { name } = req.body;
30 try {
31 const result = await pool.query(
32 'INSERT INTO items (name) VALUES ($1) RETURNING *',
33 [name]
34 );
35 res.status(201).json(result.rows[0]);
36 } catch (err) {
37 res.status(500).json({ error: err.message });
38 }
39});
40
41const PORT = process.env.PORT || 3000;
42app.listen(PORT, () => {
43 console.log(`API running on port ${PORT}`);
44});
45EOF#backend/Dockerfile
bash
1cat << 'EOF' > backend/Dockerfile
2FROM node:20-alpine
3WORKDIR /app
4COPY package*.json ./
5RUN npm install
6COPY . .
7EXPOSE 3000
8CMD ["node", "server.js"]
9EOF#Task 3: Create Frontend (10 min)
#frontend/index.html
bash
1cat << 'EOF' > frontend/index.html
2<!DOCTYPE html>
3<html lang="en">
4<head>
5 <meta charset="UTF-8">
6 <title>Docker Fullstack Demo</title>
7 <style>
8 body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
9 input { padding: 10px; width: 300px; }
10 button { padding: 10px 20px; cursor: pointer; }
11 ul { list-style: none; padding: 0; }
12 li { padding: 10px; background: #f0f0f0; margin: 5px 0; border-radius: 5px; }
13 .status { padding: 10px; border-radius: 5px; margin-bottom: 20px; }
14 .healthy { background: #d4edda; }
15 .error { background: #f8d7da; }
16 </style>
17</head>
18<body>
19 <h1>Docker Fullstack Demo</h1>
20 <div id="status" class="status">Checking...</div>
21
22 <h2>Add Item</h2>
23 <input type="text" id="itemName" placeholder="Enter item name">
24 <button onclick="addItem()">Add</button>
25
26 <h2>Items</h2>
27 <ul id="items"></ul>
28
29 <script>
30 async function checkHealth() {
31 try {
32 const res = await fetch('/api/health');
33 const data = await res.json();
34 document.getElementById('status').textContent = 'API Status: ' + data.status;
35 document.getElementById('status').className = 'status healthy';
36 } catch (err) {
37 document.getElementById('status').textContent = 'API Error: ' + err.message;
38 document.getElementById('status').className = 'status error';
39 }
40 }
41
42 async function loadItems() {
43 try {
44 const res = await fetch('/api/items');
45 const items = await res.json();
46 document.getElementById('items').innerHTML =
47 items.map(i => '<li>' + i.name + '</li>').join('');
48 } catch (err) {
49 console.error(err);
50 }
51 }
52
53 async function addItem() {
54 const name = document.getElementById('itemName').value;
55 if (!name) return;
56 try {
57 await fetch('/api/items', {
58 method: 'POST',
59 headers: { 'Content-Type': 'application/json' },
60 body: JSON.stringify({ name })
61 });
62 document.getElementById('itemName').value = '';
63 loadItems();
64 } catch (err) {
65 console.error(err);
66 }
67 }
68
69 checkHealth();
70 loadItems();
71 </script>
72</body>
73</html>
74EOF#frontend/nginx.conf
bash
1cat << 'EOF' > frontend/nginx.conf
2server {
3 listen 80;
4 location / {
5 root /usr/share/nginx/html;
6 try_files $uri $uri/ /index.html;
7 }
8 location /api {
9 proxy_pass http://backend:3000;
10 proxy_set_header Host $host;
11 }
12}
13EOF#frontend/Dockerfile
bash
1cat << 'EOF' > frontend/Dockerfile
2FROM nginx:alpine
3COPY nginx.conf /etc/nginx/conf.d/default.conf
4COPY index.html /usr/share/nginx/html/
5EXPOSE 80
6EOF#Task 4: Create Docker Compose (10 min)
#docker-compose.yml
bash
1cat << 'EOF' > docker-compose.yml
2version: '3.8'
3
4services:
5 db:
6 image: postgres:15-alpine
7 environment:
8 POSTGRES_USER: postgres
9 POSTGRES_PASSWORD: secret
10 POSTGRES_DB: app
11 volumes:
12 - postgres_data:/var/lib/postgresql/data
13 - ./init.sql:/docker-entrypoint-initdb.d/init.sql
14 healthcheck:
15 test: ["CMD-SHELL", "pg_isready -U postgres"]
16 interval: 5s
17 timeout: 5s
18 retries: 5
19
20 backend:
21 build: ./backend
22 environment:
23 DB_HOST: db
24 DB_USER: postgres
25 DB_PASSWORD: secret
26 DB_NAME: app
27 depends_on:
28 db:
29 condition: service_healthy
30 restart: unless-stopped
31
32 frontend:
33 build: ./frontend
34 ports:
35 - "8080:80"
36 depends_on:
37 - backend
38 restart: unless-stopped
39
40volumes:
41 postgres_data:
42EOF#Database Initialization
bash
1cat << 'EOF' > init.sql
2CREATE TABLE IF NOT EXISTS items (
3 id SERIAL PRIMARY KEY,
4 name VARCHAR(255) NOT NULL,
5 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
6);
7
8INSERT INTO items (name) VALUES ('Sample Item 1'), ('Sample Item 2');
9EOF#Task 5: Run and Test (10 min)
bash
1# Build and start all services
2docker compose up -d --build
3
4# Check status
5docker compose ps
6
7# View logs
8docker compose logs -f
9
10# Test API
11curl http://localhost:8080/api/health
12curl http://localhost:8080/api/items
13
14# Open in browser
15echo "Open http://localhost:8080 in browser"#โ Success Criteria
- All three services running
- Health endpoint returns status
- Items can be added and listed
- Data persists across restarts
#๐งน Cleanup
bash
docker compose down -v
cd ~ && rm -rf docker-fullstack