Python alkalmazás dockerizálása lépésről lépésre
Csomagold konténerbe a Python (Flask/FastAPI) alkalmazásod: helyes alapkép-választás, requirements cache, nem-root felhasználó és karcsú végső image.
A Python az egyik legnépszerűbb nyelv webes API-k és háttérszolgáltatások írásához, és pont ezért érdemes konténerbe csomagolni: a Python verziók, a rendszerkönyvtárak és a virtualenv-ek dzsungelét a Docker egyetlen, hordozható image-be zárja. Ebben a cikkben egy valódi Flask (és FastAPI) alkalmazást dockerizálunk — kiválasztjuk a megfelelő alapképet, cache-barátra építjük a függőségtelepítést, nem root felhasználót használunk, és produkciós alkalmazásszervert teszünk a konténerbe.
A példa alkalmazás
Induljunk egy minimális Flask appból. A projekt szerkezete:
my-api/
├── requirements.txt
├── app.py
Az app.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.get("/")
def index():
return jsonify(message="Helló a konténerből!")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
A requirements.txt:
flask==3.0.3
gunicorn==22.0.0
A gunicorn lesz a produkciós WSGI-szerver — a Flask beépített fejlesztői szervere nem éles használatra való. Ha a Dockerfile alapjait még csiszolnád, nézd meg a Dockerfile írása lépésről lépésre cikket.
A .dockerignore
Mint mindig, itt is a .dockerignore-ral kezdünk, hogy a build kontextus tiszta maradjon:
__pycache__
*.pyc
*.pyo
.venv
venv
.env
.git
.pytest_cache
.mypy_cache
⚠️ Figyelem: A
.venv(virtualenv) mappát mindenképp hagyd ki! Egy hoston létrehozott virtualenv abszolút útvonalakat és platformfüggő binárisokat tartalmaz, amelyek a konténerben elromlanak. A függőségeket a konténerben telepítjük újra, tisztán.
Az alap Dockerfile
A Dockerfile sorrendje itt is a cache-elésről szól — a requirements.txt a forrás elé kerül:
FROM python:3.12-slim
WORKDIR /app
# Tisztább, kiszámíthatóbb Python-viselkedés konténerben
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Először csak a függőséglista
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Most jön a forráskód
COPY . .
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
A fontos döntések:
python:3.12-slim– aslimváltozat lényegesen kisebb a teljes image-nél, de aalpine-nal ellentétben glibc-alapú, így nem ütközöl bele a Python natív csomagok (pl.numpy,psycopg2) fordítási gondjaiba. Pythonhoz aslimáltalában a legjobb arany középút.COPY requirements.txta forrás előtt – amíg a függőséglistád nem változik, apip installréteg a cache-ből jön, és nem telepít újra mindent minden kódváltozásnál.pip install --no-cache-dir– a pip letöltési cache-ét nem visszük be az image-be, így kisebb marad.PYTHONUNBUFFERED=1– a logok azonnal megjelennek, nem ragadnak be a pufferbe; ez konténerben aranyat ér a hibakereséshez.
Build és futtatás
docker build -t my-api:1.0 .
docker run -d -p 8000:8000 --name my-api my-api:1.0
A http://localhost:8000 címen máris válaszol a gunicorn által kiszolgált alkalmazás.
Nem root felhasználó
Alapból a konténer root-ként fut, ami felesleges kockázat. Hozzunk létre egy dedikált felhasználót, és váltsunk rá:
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Dedikált, jogosultság nélküli felhasználó létrehozása
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
💡 Tipp: A
USER appuserlegyen a függőségtelepítés után, de a futtatás előtt. Így a telepítéshez még megvannak a root-jogok, de maga az alkalmazás már korlátozott jogosultságokkal fut — ez a biztonság egyik legolcsóbb nyeresége.
FastAPI változat
Ha Flask helyett FastAPI-t használsz, csak a szerver és a parancs változik. A requirements.txt:
fastapi==0.111.0
uvicorn[standard]==0.30.1
Az alkalmazás (app.py) és a CMD:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def index():
return {"message": "Helló a konténerből!"}
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
A FastAPI aszinkron, ezért a uvicorn (ASGI-szerver) a megfelelő futtató. Nagyobb terhelésnél a gunicorn-t is használhatod uvicorn workerekkel, de a fenti egy worker is bőven elég az induláshoz.
Multi-stage build (opcionális, karcsúsításhoz)
Ha natív kiterjesztéseket fordító csomagokat használsz, a fordítóeszközöket nem érdemes a végső image-be vinni. Egy multi-stage build a függőségeket egy builder szakaszban telepíti, majd csak a kész csomagokat másolja át:
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1
COPY --from=builder /install /usr/local
COPY . .
RUN useradd --create-home appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
Így a végső image csak a futtatáshoz szükséges dolgokat tartalmazza.
Virtualenv vs. konténer
Gyakori kérdés: kell-e virtualenv a konténerben? Röviden: nem feltétlenül. A konténer maga is egy izolált környezet, így a globális pip install itt teljesen rendben van — nincs más Python-projekt, amivel ütközhetne. A virtualenv-et a hoston a .dockerignore-ral kihagyod, a konténerben pedig a rendszerszintű telepítés a bevett gyakorlat.
Összefoglalás
A Python alkalmazások dockerizálásának receptje letisztult: válassz python:3.12-slim alapot, kezdd a .dockerignore-ral (a __pycache__ és a .venv menjen ki), másold be előbb a requirements.txt-t a cache kedvéért, telepíts --no-cache-dir-rel, futtass nem root felhasználóként, és tegyél a konténerbe produkciós szervert (gunicorn Flaskhoz, uvicorn FastAPI-hoz). A virtualenv-et nyugodtan elhagyhatod — a konténer maga az izoláció. Ha natív csomagokkal dolgozol, egy multi-stage build tovább karcsúsítja a végeredményt.
Ha most kezded, nézd meg a Kezdő lépések útmutatót és a Telepítés oldalt, illetve hogy a konténerek hogyan biztosítják ezt az izolációt, olvasd el a Hogyan működik cikket. Fogd a saját Flask vagy FastAPI appodat, és próbáld ki ezt a Dockerfile-t még ma!