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:
@@ -0,0 +1,6 @@
|
||||
*
|
||||
!pyproject.toml
|
||||
!uv.lock
|
||||
!data/
|
||||
!bindery/
|
||||
**/__pycache__
|
||||
+31
@@ -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"]
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
bindery:
|
||||
build: .
|
||||
image: bindery:latest
|
||||
# The user and group that own ./config and can write CWA's ingest folder. Usually the same as CWA's.
|
||||
user: "1000:1000"
|
||||
environment:
|
||||
# The address of CWA, as the Bindery container reaches it, and the CWA account that Bindery uses.
|
||||
CWA_URL: http://calibre-web-automated:8083
|
||||
CWA_USERNAME: bindery
|
||||
CWA_PASSWORD: change-me
|
||||
# none, cwa (log in with a CWA user) or proxy (a reverse proxy sets a user header)
|
||||
AUTH_METHOD: cwa
|
||||
# Optional and independent. app.db is for logins, metadata.db is for book IDs.
|
||||
# CWA_DB_PATH: /cwa-config/app.db
|
||||
# CALIBRE_DB_PATH: /calibre-library/metadata.db
|
||||
# URL_BASE: /bindery
|
||||
# true only if clients reach Bindery through a reverse proxy alone. The login lockout then uses X-Forwarded-For.
|
||||
# TRUST_PROXY: "true"
|
||||
# BINDERY_API_KEY: a-long-random-string
|
||||
ports:
|
||||
- 8085:8085
|
||||
restart: unless-stopped
|
||||
# /api/health needs no login, and works with and without URL_BASE.
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8085/api/health', timeout=5)"]
|
||||
interval: 60s
|
||||
timeout: 10s
|
||||
start_period: 30s
|
||||
retries: 3
|
||||
volumes:
|
||||
# CWA's ingest folder. New stories and updates go here.
|
||||
- /path/to/cwa/ingest:/cwa-book-ingest
|
||||
# The story database and personal.ini. Create the folder before the first start.
|
||||
- ./config:/config
|
||||
# Optional read-only folders. Include SQLite journal files beside each database.
|
||||
# Mount the library that receives the ingest files, not a copy or another library.
|
||||
# - /path/to/cwa/config:/cwa-config:ro
|
||||
# - /path/to/calibre/library:/calibre-library:ro
|
||||
Reference in New Issue
Block a user