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
| শব্দ | মানে | 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
# ---- ধাপ ১: 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"]প্রতিটা সিদ্ধান্তের কারণ:
slimimage: পুরো 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 এ না যায়:
.venv
.git
__pycache__
.env
tests
*.md
.pytest_cachedocker 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 এ সব এক জায়গায়:
# 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: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 চালাও:
# 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 এর ভালো অভ্যাস
- এক container, এক কাজ। API আলাদা, worker আলাদা, database আলাদা।
- Stateless। File container এর disk এ না, object storage এ। (Scaling)
- Log stdout এ। File এ না। Docker আর platform গুলো stdout থেকে log জোগাড় করে।
- Config environment থেকে। একই image dev, staging, production এ, শুধু environment variable আলাদা।
- Image এ version tag।
notes-api:latestএর বদলেnotes-api:1.4.2বা git commit hash, যাতে জানো কোন version চলছে আর দরকারে ফিরে যেতে পারো। - 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 ভরে যাবে।