توی این درس یاد میگیری یک اپ Python (یک API ساده با Flask) را داکرایز کنی و تصمیمهای اصلی را بفهمی: image پایهی slim یا alpine، نصب وابستگیها با pip install --no-cache-dir -r requirements.txt و اثرش روی حجم، اجرا با gunicorn (سرور production) بهجای سرور توسعهی Flask، کاربر غیر root، و متغیرهای PYTHONUNBUFFERED و PYTHONDONTWRITEBYTECODE. در پایان، یک اپ Flask را کامل با Dockerfile اجرا و تست میکنی.
تشبیه: انتخاب وسیلهی نقلیه
Section titled “تشبیه: انتخاب وسیلهی نقلیه”python:3.12 کامل مثل اتوبوس بزرگ است: همهچیز در آن هست ولی سنگین. python:3.12-slim مثل ون است: سبکتر و برای بیشتر کارها کافی. python:3.12-alpine مثل دوچرخهی کارگو است: کوچکترین، ولی بعضی بارها (کتابخانههای کامپایلی) سختتر حمل میشوند. معمولاً slim انتخاب امن اول است.
slim یا alpine؟
Section titled “slim یا alpine؟”مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: اپ Flask
Section titled “مثال ۱: اپ Flask”from flask import Flask, jsonifyimport os, sys
app = Flask(__name__)app.json.ensure_ascii = False # نمایش درست متن فارسی در JSON
@app.get("/")def home(): print("درخواست به / رسید", flush=False) return jsonify(message="سلام از Flask در داکر")
@app.get("/health")def health(): return jsonify(status="ok", user=os.getuid(), python=sys.version.split()[0])flask==3.0.3gunicorn==22.0.0فایل requirements.txt وابستگیها را با نسخهی دقیق (==) قفل میکند؛ معادل package-lock.json در دنیای Node (برای تکرارپذیری build).
مثال ۲: Dockerfile (python:slim)
Section titled “مثال ۲: Dockerfile (python:slim)”FROM python:3.12-slim
# لاگها فوراً در docker logs دیده شوند؛ فایلهای .pyc ساخته نشوندENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
# اول وابستگیها (برای کش)، بدون کش pip داخل imageCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# کاربر غیر rootRUN useradd -m -u 1001 appuserUSER appuser
EXPOSE 8000CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]__pycache__*.pyc.venv.env.gitENV PYTHONUNBUFFERED=1: خروجیprintبدون بافر مستقیم به stdout میرود، پس درdocker logsفوراً دیده میشود.ENV PYTHONDONTWRITEBYTECODE=1: Python فایلهای.pycنمینویسد (image تمیزتر).RUN pip install --no-cache-dir: pip فایلهای دانلودشده را داخل image نگه نمیدارد (حجم کمتر).CMD ["gunicorn", ...]: سرور production (فرم exec)، روی0.0.0.0تا از بیرون کانتینر در دسترس باشد.app:appیعنی «ماژولapp، متغیرapp».
مثال ۳: build و اجرا
Section titled “مثال ۳: build و اجرا”docker build -t lx-py-app ./app 2>&1 | grep -E '^#[0-9]+ \[[0-9]/[0-9]\]' | sed -E 's/@sha256:[0-9a-f]+//' | awk '!s[$0]++'docker run -d --name lx-py1 -p 8370:8000 lx-py-app >/dev/null; sleep 3curl -s localhost:8370/curl -s localhost:8370/healthdocker logs lx-py1 2>&1 | grep -E "Listening|درخواست" | head -3#5 [1/6] FROM docker.io/library/python:3.12-slim#6 [3/6] COPY requirements.txt .#7 [2/6] WORKDIR /app#8 [4/6] RUN pip install --no-cache-dir -r requirements.txt#9 [5/6] COPY . .#10 [6/6] RUN useradd -m -u 1001 appuser{"message":"سلام از Flask در داکر"}{"python":"3.12.15","status":"ok","user":1001}[2026-10-03 11:06:32 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)درخواست به / رسیدAPI جواب داد و /health نشان میدهد پروسه با uid ۱۰۰۱ (کاربر appuser، غیر root) اجرا میشود و نسخهی Python چیست. در لاگها خط «Listening at» از gunicorn و print برنامه را فوراً میبینیم (بهخاطر PYTHONUNBUFFERED).
PYTHONUNBUFFERED=1 چه کمکی میکند؟
بدون آن ممکن است خروجی برنامه در بافر بماند و دیر یا اصلاً در لاگ دیده نشود.
مثال ۴: slim در برابر alpine (اندازهها)
Section titled “مثال ۴: slim در برابر alpine (اندازهها)”همان برنامه را با پایهی alpine هم میسازیم:
sed 's/python:3.12-slim/python:3.12-alpine/; s/RUN useradd -m -u 1001 appuser/RUN adduser -D -u 1001 appuser/' app/Dockerfile > app/Dockerfile.alpinedocker build -q -f app/Dockerfile.alpine -t lx-py-alpine ./app >/dev/null 2>&1docker images --format 'table {{.Repository}}\t{{.Size}}' | grep -E "REPOSITORY|lx-py-(app|alpine)"REPOSITORY SIZElx-py-alpine 97.8MBlx-py-app 224MBنسخهی alpine چند برابر کوچکتر است. ولی alpine از musl استفاده میکند (بهجای glibc) و بسیاری از بستههای Python که کد C دارند برایش wheel آماده ندارند، پس باید در زمان build کامپایل شوند (کند و نیاز به ابزار ساخت). برای یک اپ سادهی Flask مشکلی نیست؛ برای numpy، pandas، psycopg2 و مانند آن slim مطمئنتر است. قاعدهی عملی: با slim شروع کن و فقط در صورت نیاز و آزمایش به alpine برو.
مثال ۵: --no-cache-dir و حجم
Section titled “مثال ۵: --no-cache-dir و حجم”sed 's/pip install --no-cache-dir -r/pip install -r/' app/Dockerfile > app/Dockerfile.cachedocker build -q -f app/Dockerfile.cache -t lx-py-cache ./app >/dev/null 2>&1docker images --format 'table {{.Repository}}\t{{.Size}}' | grep -E "REPOSITORY|lx-py-(app|cache)"REPOSITORY SIZElx-py-cache 227MBlx-py-app 224MBبدون --no-cache-dir، pip فایلهای wheel دانلودشده را در ~/.cache/pip نگه میدارد و داخل لایهی image ذخیره میشود (حجم اضافه بدون هیچ سودی). در image همیشه --no-cache-dir.
مثال ۶: سرور توسعهی Flask و دام localhost
Section titled “مثال ۶: سرور توسعهی Flask و دام localhost”docker run -d --name lx-py2 -p 8371:5000 -v "$PWD/app":/app -w /app python:3.12-slim sh -c 'pip install -q flask==3.0.3 >/dev/null 2>&1 && flask --app app run --port 5000' >/dev/nullsleep 14echo "پیشفرض flask run (فقط localhost داخل کانتینر):"curl -s -m 5 -o /dev/null -w " HTTP %{http_code}\n" localhost:8371/ || echo " (کد خروج curl: $?)"docker logs lx-py2 2>&1 | grep -E "WARNING|Running on" | head -3 | cut -c1-90docker rm -f lx-py2 >/dev/nullپیشفرض flask run (فقط localhost داخل کانتینر): HTTP 000 (کد خروج curl: 56)WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:5000flask run بهصورت پیشفرض فقط روی 127.0.0.1 داخل کانتینر گوش میدهد، پس از بیرون در دسترس نیست (درس پورتها)، و همین لاگ هشدار میدهد این سرور برای production نیست. راهحل: در production از gunicorn استفاده کن؛ در توسعه flask run --host 0.0.0.0.
پشت پرده
Section titled “پشت پرده”wheel، musl و glibc
Section titled “wheel، musl و glibc”بستههای Python معمولاً به شکل wheel (فایل از پیشساخته) توزیع میشوند. برای کتابخانههای دارای کد C، wheel ها برای glibc (اغلب لینوکسها، از جمله Debian و slim) ساخته میشوند. Alpine از musl استفاده میکند و اگر wheel مناسبی نباشد، pip بسته را از سورس کامپایل میکند که به compiler و هدرها نیاز دارد. این همان دلیل کندی build و بزرگ شدن image در alpine برای بعضی پروژههاست.
gunicorn چرا؟
Section titled “gunicorn چرا؟”سرور توسعهی Flask تکنخی و بدون مدیریت مناسب خطا و بار است. gunicorn یک سرور WSGI است که چند worker میسازد، پروسهها را مدیریت میکند و سیگنالها (مثل SIGTERM) را درست میگیرد؛ به همین دلیل بهعنوان CMD در فرم exec یک انتخاب استاندارد است.
جدولهای مرجع
Section titled “جدولهای مرجع”| انتخاب | مزیت | نکته |
|---|---|---|
python:3.12 |
همهچیز دارد | حجیم |
python:3.12-slim |
سبک و سازگار | انتخاب پیشفرض |
python:3.12-alpine |
کوچکترین | musl؛ گاهی نیاز به کامپایل |
| تنظیم | معنی |
|---|---|
PYTHONUNBUFFERED=1 |
خروجی بدون بافر (لاگ فوری) |
PYTHONDONTWRITEBYTECODE=1 |
نساختن .pyc |
pip install --no-cache-dir |
بدون ذخیرهی کش pip در image |
pip install -r requirements.txt |
نصب مطابق فایل |
gunicorn --bind 0.0.0.0:8000 app:app |
سرور production |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) فایل requirements.txt نیست
Section titled “۱) فایل requirements.txt نیست”mkdir -p m1 && printf 'FROM python:3.12-slim\nCOPY requirements.txt .\nRUN pip install -r requirements.txt\n' > m1/Dockerfiledocker build -t lx-py-dev ./m1 2>&1 | grep -E 'ERROR' | head -1 | sed -E 's/ref [^ ]+/ref .../' | cut -c1-130#6 ERROR: failed to calculate checksum of ref ... "/requirements.txt": not foundCOPY فایل را پیدا نکرد. راهحل: pip freeze > requirements.txt (یا ساخت دستی) و در context بگذار.
۲) وابستگی بدون نسخه
Section titled “۲) وابستگی بدون نسخه”flask (بدون ==) در هر build آخرین نسخه را میگیرد و تکرارپذیری را از بین میبرد. راهحل: نسخهی دقیق، یا ابزارهایی مثل pip-tools.
۳) اجرای سرور توسعه در production
Section titled “۳) اجرای سرور توسعه در production”مثال ۶: هشدار میدهد و فقط روی localhost است. راهحل: gunicorn.
۴) لاگهای بافرشده
Section titled “۴) لاگهای بافرشده”بدون PYTHONUNBUFFERED، print ها گاهی دیر در docker logs ظاهر میشوند. راهحل: ENV PYTHONUNBUFFERED=1.
۵) COPY . . قبل از نصب وابستگیها
Section titled “۵) COPY . . قبل از نصب وابستگیها”هر تغییر کد، pip install را دوباره اجرا میکند. راهحل: اول COPY requirements.txt، pip install، بعد COPY . . (درس لایهها و کش).
بدون ساختن image، با یک دستور docker run --rm نسخهی Python داخل python:3.12-slim را چاپ کن.
دیدن جواب
docker run --rm python:3.12-slim python --versionPython 3.12.15تمرین اصلی: یک اپ Flask را داکرایز کن. اپ app را با docker build -t lx-py-app ./app بساز، روی پورت 8372 اجرا کن و / را بخوان.
دیدن جواب
docker build -q -t lx-py-app ./app >/dev/null 2>&1docker run -d --name lx-py3 -p 8372:8000 lx-py-app >/dev/null; sleep 3curl -s localhost:8372/docker rm -f lx-py3 >/dev/null{"message":"سلام از Flask در داکر"}اندازهی image ساختهشده با slim و alpine را در یک جدول کنار هم بگذار و توضیح بده چرا alpine با وجود کوچکتر بودن همیشه بهترین انتخاب نیست.
دیدن جواب
docker images --format 'table {{.Repository}}\t{{.Size}}' | grep -E "REPOSITORY|lx-py-(app|alpine)"REPOSITORY SIZElx-py-app 224MBlx-py-alpine 97.8MBalpine کوچکتر است، ولی بهجای glibc از musl استفاده میکند. بستههای دارای کد C (مثل numpy و psycopg2) ممکن است wheel آماده برای musl نداشته باشند و باید با ابزار ساخت کامپایل شوند (build کند و image بزرگتر). slim معمولاً سازگارتر و پیشبینیپذیرتر است.
آزمونک
Section titled “آزمونک”pip install --no-cache-dir در Dockerfile چه سودی دارد؟
کش pip در image فایدهای ندارد.
چرا در production بهجای flask run از gunicorn استفاده میکنیم؟
خود Flask هم هشدار میدهد.
flask run در کانتینر از بیرون در دسترس نیست چون…
درس پورتها.
alpine چرا گاهی مشکلساز است؟
wheel های glibc روی musl کار نمیکنند.
PYTHONUNBUFFERED=1 برای چیست؟
خروجی بافر نمیشود.
جمعبندی
Section titled “جمعبندی”- ساختار:
FROM python:3.12-slim←ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1←COPY requirements.txt←pip install --no-cache-dir -r←COPY . .← کاربر غیر root ←CMD ["gunicorn", ...]. - slim انتخاب پیشفرض؛ alpine کوچکتر ولی با musl (گاهی نیاز به کامپایل).
- نسخهی وابستگیها را با
==قفل کن. - gunicorn برای production؛ برنامه روی
0.0.0.0گوش بدهد. .dockerignore:__pycache__،.venv،.env،.git.
| دستور | کاری که میکند |
|---|---|
pip install --no-cache-dir -r requirements.txt | نصب بدون کش pip |
pip freeze > requirements.txt | ساخت فایل وابستگیها |
ENV PYTHONUNBUFFERED=1 | لاگ بدون بافر |
ENV PYTHONDONTWRITEBYTECODE=1 | نساختن .pyc |
gunicorn --bind 0.0.0.0:8000 app:app | سرور production |
flask --app app run --host 0.0.0.0 | سرور توسعه که از بیرون در دسترس است |
docker build -t py-app . | ساخت image |
docker run -d -p 8000:8000 py-app | اجرا با پورت |