Add the container image and compose file

Gunicorn runs one worker with threads, because the job threads run
inside it. The compose file mounts only CWA's ingest folder and the
config folder.
This commit is contained in:
tim
2026-09-27 23:38:28 +02:00
parent 2f4271314b
commit 7c61bbd1e4
3 changed files with 76 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# syntax=docker/dockerfile:1
FROM ghcr.io/astral-sh/uv:python3.14-trixie-slim
# Prevents Python from buffering stdout and stderr
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PATH=/app/.venv/bin:$PATH
WORKDIR /app
# uv installs the locked dependencies without the dev tools. The app comes after them, so a code change reuses this
# layer.
COPY pyproject.toml uv.lock ./
RUN UV_COMPILE_BYTECODE=1 UV_PYTHON_DOWNLOADS=never \
uv sync --locked --no-default-groups --no-install-project --no-cache
# --chmod sets the mode as part of the copy, so every user can read the app. The container can
# run as any user, also with rootless Docker or Podman, and root owns the copies otherwise.
COPY --chmod=a+rX data ./data
COPY --chmod=a+rX bindery ./bindery
EXPOSE 8085
# The commands come from /app/.venv/bin. "uv run" would try to sync the root-owned environment as the container user.
HEALTHCHECK --interval=60s --timeout=10s --start-period=30s --retries=3 \
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8085/api/health', timeout=5)"]
# One worker only, because the job and scheduler threads run inside it. Bindery does not use the control socket, and
# its default folder, $HOME/.gunicorn, is not writable for a container user without a home folder.
CMD ["gunicorn", "--bind", "0.0.0.0:8085", "--workers", "1", "--threads", "4", "--timeout", "300", \
"--no-control-socket", "bindery.web:app"]