Ctrl K
ComunidadGuías…

Dockerfile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ ./app/
COPY app/static/ ./static/

# Catalogues i18n : les .po sont versionnés, les .mo sont compilés ici. Un
# catalogue mal formé fait échouer le build plutôt que de partir en production
# avec des traductions muettes. Voir docs/i18n.md.
RUN pybabel compile -d app/translations

# Compte de service dédié : rien ici n'a besoin de root, et une exécution en root
# transforme une évasion de conteneur en compromission de l'hôte. UID fixe (et
# non attribué au hasard) pour que le propriétaire du volume de données reste le
# même d'une image à la suivante.
#
# ATTENTION à la mise à jour d'une instance existante : son volume appartient à
# root, l'application ne pourra plus y écrire. Une fois, conteneur arrêté :
#   docker run --rm -v excerpta_data:/data alpine chown -R 10001:0 /data
RUN useradd --system --uid 10001 --gid 0 --no-create-home excerpta \
    && mkdir -p data \
    && chown -R 10001:0 /app \
    && chmod -R g=u /app

ARG GIT_COMMIT=unknown
ENV APP_VERSION=$GIT_COMMIT

# Confiance aux en-têtes X-Forwarded-* : uvicorn s'en sert pour
# `request.client.host`, donc pour la limitation de débit, et pour le schéma
# d'URL. Il parcourt X-Forwarded-For depuis la droite et retient la première
# adresse qui n'est PAS dans cette liste : celle que le reverse proxy a ajoutée.
#
# Jamais `*` : uvicorn prend alors le PREMIER élément de la liste, c'est-à-dire
# celui qu'écrit le client. Changer cette valeur à chaque requête suffisait à
# repartir d'un compteur neuf et annulait toutes les limites de débit.
#
# Par défaut : boucle locale et réseaux privés, où se trouvent le proxy et la
# passerelle Docker. Restreindre à l'adresse exacte du proxy quand elle est
# connue (voir FORWARDED_ALLOW_IPS dans .env.example).
ENV FORWARDED_ALLOW_IPS=127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7

USER 10001

EXPOSE 8000

# L'en-tête Host doit porter le hostname de BASE_URL : StrictHostMiddleware
# répond 400 à tout le reste, y compris à un appel sur 127.0.0.1 — un
# healthcheck naïf resterait rouge en permanence.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
    CMD python -c "import os, sys, urllib.request as u; from urllib.parse import urlparse; \
host = urlparse(os.environ.get('BASE_URL', 'http://localhost')).hostname or 'localhost'; \
sys.exit(0 if u.urlopen(u.Request('http://127.0.0.1:8000/health', headers={'Host': host}), timeout=4).status == 200 else 1)"

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