Ravindra BagaleCourses & study guides मराठी

Chapter 73: DevOps Projects Part 3: Docker Websites, APIs, Compose, Registry and ECR

73.7 Project 23: Three-tier app with Docker Compose (frontend, API, database)

Friends, real apps are more than one container. Today we write a Compose file with frontend, API and Postgres, put them on one network, keep DB data in a named volume, and prove all three tiers with curl and psql.

What you will build in this project

  1. frontend, api and db services.
  2. Named volume pgdata.
  3. Network shopnet.
  4. curl + psql proof.

1. Start from a real company story

At Zomato in Gurugram, each engineer ran frontend, API and MySQL with three different scripts. One person used the wrong DB password and wiped a shared laptop database.

A demo for investors failed live. Rough cost: about ₹8 lakh in delayed diligence and overtime.

Sara moved the stack into one compose.yaml with a named volume and service DNS names.

Today we build that three-tier Compose lab.

frontendnginx :8080apipython :3000dbpostgres volumecompose upcurl + psql
Picture 1: three services, one compose file

Ravindra Bagale's Tip

Many students put passwords in the compose file and commit them. For labs use a throwaway password; for work use secrets or an untracked env file. Keep that habit.

2. Start and prepare

Step 1: Write frontend

Why: UI needs a static root. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: index.html. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 1: fe

Type this:

mkdir -p /tmp/tier/frontend && printf '%s\n' '<!doctype html><title>shop</title><h1>shop ui</h1>' > /tmp/tier/frontend/index.html && cat /tmp/tier/frontend/index.html

You will see:

<!doctype html><title>shop</title><h1>shop ui</h1>

What it means: UI.

Example 2: df fe

Type this:

printf '%s\n' 'FROM nginx:1.27-alpine' 'COPY index.html /usr/share/nginx/html/index.html' > /tmp/tier/frontend/Dockerfile && cat /tmp/tier/frontend/Dockerfile

You will see:

FROM nginx:1.27-alpine
COPY ...

What it means: FE image.

Example 3: ls

Type this:

ls /tmp/tier/frontend

You will see:

Dockerfile
index.html

What it means: Ready.

Step 2: Write API stub

Why: API is the middle tier. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: tiny API container. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 4: api

Type this:

mkdir -p /tmp/tier/api && printf '%s\n' 'from http.server import BaseHTTPRequestHandler,HTTPServer' 'class H(BaseHTTPRequestHandler):' '  def do_GET(self):' '    self.send_response(200); self.end_headers(); self.wfile.write(b"{\"ok\":true}")' 'HTTPServer(("0.0.0.0",3000),H).serve_forever()' > /tmp/tier/api/app.py && wc -l /tmp/tier/api/app.py

You will see:

6 /tmp/tier/api/app.py

What it means: Tiny API.

Example 5: df api

Type this:

printf '%s\n' 'FROM python:3.12-alpine' 'WORKDIR /app' 'COPY app.py .' 'CMD ["python","app.py"]' > /tmp/tier/api/Dockerfile && head -2 /tmp/tier/api/Dockerfile

You will see:

FROM python:3.12-alpine
WORKDIR /app

What it means: API image.

Example 6: ls

Type this:

ls /tmp/tier/api

You will see:

Dockerfile
app.py

What it means: Ready.

Step 3: Write compose file

Why: Compose describes the whole stack. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: services: keys. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 7: compose

Type this:

printf '%s\n' 'services:' '  frontend:' '    build: ./frontend' '    ports: ["8080:80"]' '    depends_on: [api]' '  api:' '    build: ./api' '    ports: ["3000:3000"]' '    depends_on: [db]' '    environment:' '      DATABASE_URL: postgres://shop:shop@db:5432/shop' '  db:' '    image: postgres:16-alpine' '    environment:' '      POSTGRES_USER: shop' '      POSTGRES_PASSWORD: shop' '      POSTGRES_DB: shop' '    volumes: ["pgdata:/var/lib/postgresql/data"]' 'volumes:' '  pgdata:' 'networks:' '  default:' '    name: shopnet' > /tmp/tier/compose.yaml && wc -l /tmp/tier/compose.yaml

You will see:

24 /tmp/tier/compose.yaml

What it means: Compose written.

Example 8: grep services

Type this:

grep -E 'frontend|api|db:' /tmp/tier/compose.yaml | head -5

You will see:

frontend:/api:/db:

What it means: Three services.

Example 9: mode

Type this:

stat -c '%a' /tmp/tier/compose.yaml

You will see:

644

What it means: Safe.

3. Build the main pieces

Step 4: Add postgres service

Why: Database must persist. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: image postgres. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 10: db image

Type this:

grep postgres /tmp/tier/compose.yaml | head -1

You will see:

image: postgres:16-alpine

What it means: Pinned DB.

Example 11: env user

Type this:

grep POSTGRES_USER /tmp/tier/compose.yaml

You will see:

POSTGRES_USER: shop

What it means: User.

Example 12: no secret file

Type this:

echo 'Lab password is shop; real life uses secrets or env files not committed'

You will see:

Lab password is shop; real life uses secrets or env files not committed

What it means: Habit.

Step 5: Add volumes

Why: Volumes keep data. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: volumes: block. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 13: vol

Type this:

grep pgdata /tmp/tier/compose.yaml

You will see:

volumes: ["pgdata:..."] / volumes: / pgdata:

What it means: Named volume.

Example 14: why vol

Type this:

echo 'Without a volume, DB data dies when the container is removed'

You will see:

Without a volume, DB data dies when the container is removed

What it means: Reason.

Example 15: ls vol later

Type this:

echo 'docker volume ls | grep pgdata'

You will see:

docker volume ls | grep pgdata

What it means: Check later.

Step 6: Add network

Why: Network lets services talk by name. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: networks: block. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 16: net

Type this:

grep shopnet /tmp/tier/compose.yaml

You will see:

name: shopnet

What it means: Custom name.

Example 17: dns tip

Type this:

echo 'api reaches db by hostname db on the compose network'

You will see:

api reaches db by hostname db on the compose network

What it means: DNS.

Example 18: depends

Type this:

grep depends_on /tmp/tier/compose.yaml | head -2

You will see:

depends_on: [api]/depends_on: [db]

What it means: Order hint.

4. Automate and verify

Step 7: docker compose up

Why: up starts all. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: compose up -d. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 19: up

Type this:

cd /tmp/tier && docker compose -f compose.yaml up -d --build

You will see:

... Started ...

What it means: Stack up.

Example 20: ps

Type this:

cd /tmp/tier && docker compose -f compose.yaml ps

You will see:

NAME ... STATUS ... PORTS

What it means: Three rows.

Example 21: vol ls

Type this:

docker volume ls | grep pgdata || echo 'volume_pending'

You will see:

...pgdata

What it means: Volume.

Step 8: Check ps

Why: ps shows health. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: compose ps. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 22: ps fmt

Type this:

cd /tmp/tier && docker compose -f compose.yaml ps --format 'table {{.Name}} {{.Status}}' | head -5

You will see:

NAME STATUS

What it means: Statuses.

Example 23: count

Type this:

cd /tmp/tier && docker compose -f compose.yaml ps -q | wc -l

You will see:

3

What it means: Three containers.

Example 24: net ls

Type this:

docker network ls | grep shopnet

You will see:

shopnet

What it means: Network.

Step 9: curl frontend

Why: Browser path via curl. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: curl :8080. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 25: fe curl

Type this:

curl -s http://127.0.0.1:8080/

You will see:

<!doctype html><title>shop</title><h1>shop ui</h1>

What it means: UI.

Example 26: code

Type this:

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/

You will see:

200

What it means: OK.

Example 27: title

Type this:

curl -s http://127.0.0.1:8080/ | grep -o shop

You will see:

shop

What it means: Body.

5. Harden and accept

Step 10: curl API

Why: API path. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: curl :3000. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 28: api curl

Type this:

curl -s http://127.0.0.1:3000/

You will see:

{"ok":true}

What it means: API.

Example 29: api code

Type this:

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/

You will see:

200

What it means: OK.

Example 30: env

Type this:

cd /tmp/tier && docker compose -f compose.yaml exec -T api printenv DATABASE_URL

You will see:

postgres://shop:shop@db:5432/shop

What it means: API sees DB URL.

Step 11: DB exec check

Why: psql or exec proves DB. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: exec psql. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 31: db

Type this:

cd /tmp/tier && docker compose -f compose.yaml exec -T db psql -U shop -d shop -c 'select 1 as ok;'

You will see:

ok
--
1

What it means: DB answers.

Example 32: tables

Type this:

cd /tmp/tier && docker compose -f compose.yaml exec -T db psql -U shop -d shop -c '\dt' || echo 'no_tables_yet'

You will see:

no_tables_yet

What it means: Empty schema OK.

Example 33: logs db

Type this:

cd /tmp/tier && docker compose -f compose.yaml logs db --tail 3

You will see:

database system is ready

What it means: Ready.

Step 12: Acceptance checklist

Why: Sara signs off when all three tiers answer. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the stack is healthy today.

What: All green. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn Compose and registries.

Example 34: down note

Type this:

echo 'docker compose -f compose.yaml down keeps the named volume by default'

You will see:

docker compose -f compose.yaml down keeps the named volume by default

What it means: Persist tip.

Example 35: all 200

Type this:

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/; echo; curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/

You will see:

200
200

What it means: Both.

Example 36: Sign-off

Type this:

echo 'Sara: PASS'

You will see:

Sara: PASS

What it means: Done.

Check yourself

Q1. Who fixed Zomato demos?

Sara

Q2. DB image?

postgres:16-alpine

Q3. UI host port?

8080

Q4. API host port?

3000

Q5. Volume name?

pgdata

Q6. Compose command to start?

docker compose up -d

Friends, you ran a three-tier Compose stack with a named volume. Did you understand? Let us try it once more. Add a second API replica and put a simple load balance in front.

Lab: try at home

  1. Write frontend and API Dockerfiles.
  2. Write compose.yaml with db volume.
  3. compose up --build.
  4. curl UI and API.
  5. psql select 1.

Recap

  • Compose lists services.
  • depends_on is order not readiness.
  • Named volumes keep DB data.
  • Service names are DNS names.
  • Never commit real passwords.