Docker ও Docker Compose

যেকোনো machine এ একইভাবে চলা app

রোডম্যাপের মাস ৫পড়তে প্রায় ১২ মিনিট

"আমার machine এ তো চলে!" এই কথাটা software এর ইতিহাসের সবচেয়ে বেশি বলা অজুহাতের একটা। তোমার Mac এ Python 3.13, server এ 3.11; তোমার কাছে একটা library এর এক version, server এ আরেক। Docker এই সমস্যা মেটায়: app, তার Python, তার library, তার setting, সব একটা বাক্সে (container) ভরে দেয়। সেই বাক্স তোমার Mac, বন্ধুর laptop, আর cloud server সব জায়গায় হুবহু একইভাবে চলে।

#Image আর container

Dockerfile রেসিপি build Image রান্না করা খাবারের ছাঁচ (বদলায় না) run Container ১ (তোমার Mac) Container ২ (server A) Container ৩ (server B)
Dockerfile হলো রেসিপি, image হলো সেই রেসিপিতে বানানো স্থির ছাঁচ, container হলো চলমান একটা copy। এক image থেকে যত খুশি container।
শব্দ মানে Flutter এর সাথে তুলনা
Dockerfile Image বানানোর ধাপগুলো Build script
Image App + Python + library, স্থির Build করা APK
Container Image এর চলমান copy Phone এ চালু app
Registry Image রাখার জায়গা Play Store (Docker Hub, GHCR)

Container হলো হালকা: virtual machine এর মতো পুরো operating system না, শুধু app আর তার দরকারি জিনিস। চালু হয় কয়েক সেকেন্ডে।

#FastAPI এর জন্য Dockerfile

Dockerfile
# ---- ধাপ ১: build (package install) ----
FROM python:3.13-slim AS builder

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

# আগে শুধু dependency file: code বদলালেও এই ধাপ cache থেকে আসবে
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project

COPY . .
RUN uv sync --frozen --no-dev

# ---- ধাপ ২: চালানোর image (ছোট, পরিষ্কার) ----
FROM python:3.13-slim

RUN useradd --create-home appuser          # root হিসেবে চালাবো না
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /app /app
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
USER appuser

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2", "--proxy-headers"]

প্রতিটা সিদ্ধান্তের কারণ:

  • slim image: পুরো Debian এর বদলে ছোট version, কম আকার, কম নিরাপত্তা ঝুঁকি
  • Multi-stage build: build এর tool (compiler, uv cache) শেষ image এ যায় না, শুধু চলার জন্য যা লাগে
  • Dependency আগে, code পরে: Docker প্রতিটা ধাপ (layer) cache করে। Code বদলালে শুধু শেষের ধাপগুলো আবার চলে, package install আবার হয় না। Build ৩ মিনিট থেকে ১০ সেকেন্ড।
  • --frozen: uv.lock এ যা আছে হুবহু তাই, নতুন কিছু না
  • Non-root user: container ভেঙে কেউ ঢুকলেও সীমিত ক্ষমতা
  • PYTHONUNBUFFERED=1: log সাথে সাথে দেখা যায়

.dockerignore file (Git এর .gitignore এর মতো), যাতে অদরকারি বা গোপন জিনিস image এ না যায়:

Text
.venv
.git
__pycache__
.env
tests
*.md
.pytest_cache
Terminal
docker build -t notes-api:dev .
docker images notes-api
docker run --rm -p 8000:8000 --env-file .env notes-api:dev

#Docker Compose: কয়েকটা container একসাথে

App এর সাথে PostgreSQL আর Redis ও লাগে। প্রতিটা আলাদা docker run করা ক্লান্তিকর। compose.yaml এ সব এক জায়গায়:

YAML
# compose.yaml
services:
  api:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    environment:
      DATABASE_URL: postgresql+asyncpg://app:secret@db:5432/notes   # "db" = service এর নাম
      REDIS_URL: redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: notes
    volumes:
      - pgdata:/var/lib/postgresql/data     # data container মুছলেও থাকবে
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d notes"]
      interval: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:
Terminal
docker compose up -d --build       # সব চালু
docker compose ps                  # কে কী অবস্থায়
docker compose logs -f api         # api এর log দেখো
docker compose exec api alembic upgrade head   # container এর ভেতরে command
docker compose exec db psql -U app -d notes
docker compose down                # বন্ধ (data থাকে)
docker compose down -v             # বন্ধ, volume সহ মুছে (data যাবে!)

#Container গুলো একে অপরকে কীভাবে খোঁজে

Compose একটা ভেতরের network বানায়। প্রতিটা service তার নাম দিয়ে চেনা যায়: api container থেকে database এর ঠিকানা db:5432, localhost:5432 না। Container এর ভেতরে localhost মানে সেই container নিজে। এটা নতুনদের সবচেয়ে সাধারণ ভুল।

#Volume: data কোথায় থাকে

Container মুছে ফেললে তার ভেতরের সব কিছু যায়। Database এর data হারানো চলবে না, তাই volume এ রাখা হয়, যেটা container এর বাইরে থাকে।

#Development এর সময় code বদল সাথে সাথে

প্রতিবার code বদলে image আবার build করা ধীর। Development এ code mount করে reload চালাও:

YAML
# compose.override.yaml (শুধু local এ, compose নিজে থেকেই মিলিয়ে নেয়)
services:
  api:
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
    volumes:
      - ./src:/app/src

#Container এর ভালো অভ্যাস

  1. এক container, এক কাজ। API আলাদা, worker আলাদা, database আলাদা।
  2. Stateless। File container এর disk এ না, object storage এ। (Scaling)
  3. Log stdout এ। File এ না। Docker আর platform গুলো stdout থেকে log জোগাড় করে।
  4. Config environment থেকে। একই image dev, staging, production এ, শুধু environment variable আলাদা।
  5. Image এ version tag। notes-api:latest এর বদলে notes-api:1.4.2 বা git commit hash, যাতে জানো কোন version চলছে আর দরকারে ফিরে যেতে পারো।
  6. Health check। Platform যেন বুঝতে পারে container সুস্থ কিনা।

#অনুশীলন

#নিজেকে যাচাই করো

Image আর container এর পার্থক্য কী?

Image হলো স্থির ছাঁচ (app + সব dependency), বদলায় না। Container হলো image থেকে চালু করা একটা চলমান copy। একটা image থেকে অনেক container চালানো যায়।

Dockerfile এ dependency file আগে আর code পরে কেন copy করা হয়?

Docker প্রতিটা ধাপ cache করে, আর কোনো ধাপের input বদলালে সেই ধাপ আর তার পরের সব আবার চলে। Code বেশি বদলায়, dependency কম। আগে dependency install করলে code বদলালেও install এর ধাপ cache থেকে আসে, build অনেক দ্রুত হয়।

Compose এ api container থেকে database এর host `localhost` দিলে কেন কাজ করবে না?

Container এর ভেতরে localhost মানে সেই container নিজে। Database আলাদা container এ, compose এর network এ তার নাম (db) দিয়ে পৌঁছাতে হয়।

Database container এ volume না দিলে কী হবে?

Container মুছে ফেললে বা নতুন করে বানালে সব data হারিয়ে যাবে, কারণ data container এর ভেতরের অস্থায়ী disk এ ছিল।

চিহ্ন দিলে sidebar আর রোডম্যাপে এই অধ্যায়ের station ভরে যাবে।