From 147a3a54ef3b099001f6d265480e7e4d01c5d2e9 Mon Sep 17 00:00:00 2001 From: ajurna Date: Tue, 13 Oct 2026 10:25:07 +0100 Subject: [PATCH] **Refactor Docker and Compose for enhanced compatibility and efficiency** - Restructured `Dockerfile` to include a dedicated frontend build stage and improved runtime configurations. - Added new health checks and adjusted environment variables for better container management. - Updated cron job configurations to leverage `/opt/venv` and align with new paths. - Refined `docker-compose` files for multi-service setups, including better volume and environment variable handling. - Migrated `cbwebreader-cron` service setup and improved restart policies across the board. - Enhanced `.gitea` CI workflow to use updated tools (`uv`), streamline buildx processes, and ensure proper version extraction from metadata. --- .dockerignore | 2 -- .env.docker | 8 +++-- .env.example | 8 +++-- .gitea/workflows/build.yaml | 20 +++++------- Dockerfile | 63 ++++++++++++++++++++++++------------- cbreader/crontab | 2 +- compose/docker-compose.yml | 43 ++++++++++++++----------- docker-compose.yml | 60 ++++++++++++++++++----------------- entrypoint.sh | 8 +++-- 9 files changed, 122 insertions(+), 92 deletions(-) diff --git a/.dockerignore b/.dockerignore index 763ed10..db0a881 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ !cbreader !comic -!comic_auth !static !manage.py !pyproject.toml @@ -14,6 +13,5 @@ !package-lock.json !package.json !frontend -!uv.lock /frontend/node_modules /frontend/dist diff --git a/.env.docker b/.env.docker index 87ce7e1..35bac31 100644 --- a/.env.docker +++ b/.env.docker @@ -15,8 +15,10 @@ DB_DATABASE=cbwebreader # https://github.com/jacobian/dj-database-url DATABASE_URL=postgres://${DB_USER}:${DB_PASS}@${DB_HOST}/${DB_DATABASE} -#Path to your comics. -COMIC_BOOK_VOLUME=/media/plex/comics +# Path to your comics *inside* the container. Keep this as a container path. +COMIC_BOOK_VOLUME=/comics +# Host path that is mounted to the path above. +COMIC_BOOK_HOST_PATH=/media/plex/comics STATIC_ROOT='/static' @@ -32,4 +34,4 @@ MEDIA_ROOT='/media' # DJANGO_RECAPTCHA_PUBLIC_KEY = '' # Comment the following if not using a reverse proxy. -USE_X_FORWARDED_HOST=True \ No newline at end of file +USE_X_FORWARDED_HOST=True diff --git a/.env.example b/.env.example index b453b72..c8c7b86 100644 --- a/.env.example +++ b/.env.example @@ -15,8 +15,10 @@ DB_DATABASE=cbwebreader # https://github.com/jacobian/dj-database-url DATABASE_URL='postgres://${DB_USER}:${DB_PASS}@${DB_HOST}/${DB_DATABASE}' -# Path to your comics. -COMIC_BOOK_VOLUME=/path/to/comic/folder +# Path to your comics *inside* the container. Keep this as a container path. +COMIC_BOOK_VOLUME=/comics +# Host path that is mounted to the path above (this is the one that varies per machine). +COMIC_BOOK_HOST_PATH=/path/to/comic/folder # Usually fine tp leave as is. STATIC_ROOT=/static @@ -29,4 +31,4 @@ MEDIA_ROOT='/media' # UNRAR_TOOL = 'unrar.exe' # Comment the following if not using a reverse proxy. -USE_X_FORWARDED_HOST=True \ No newline at end of file +USE_X_FORWARDED_HOST=True diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 2d75424..622a8c4 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -12,25 +12,21 @@ jobs: run: curl -fsSL https://get.docker.com | sh - name: Set up QEMU uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx + - name: Set up Buildx uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: install pip - run: apt update && apt install -y python3-pip - - uses: abatilo/actions-poetry@v2 - with: - poetry-version: "1.6.1" - - uses: actions/setup-node@v3 - - uses: actions/checkout@v3 - # Extract version from Poetry + - uses: actions/checkout@v4 + - name: Install uv + run: pip install uv + # Extract version from the project metadata - name: Get version - run: echo "POETRY_VERSION=$(poetry version --short)" >> $GITHUB_ENV + run: echo "PROJECT_VERSION=$(uv version --short)" >> $GITHUB_ENV - name: Build and push Docker image run: | - docker build -t ajurna/cbwebreader:latest -t ajurna/cbwebreader:${{ env.POETRY_VERSION }} . - docker push ajurna/cbwebreader:${{ env.POETRY_VERSION }} + docker build -t ajurna/cbwebreader:latest -t ajurna/cbwebreader:${{ env.PROJECT_VERSION }} . + docker push ajurna/cbwebreader:${{ env.PROJECT_VERSION }} docker push ajurna/cbwebreader:latest diff --git a/Dockerfile b/Dockerfile index 570259a..c9374fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,57 @@ -FROM python:3.14-slim-trixie -COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -ENV PYTHONFAULTHANDLER=1 \ - PYTHONHASHSEED=random \ - PYTHONUNBUFFERED=1 \ - PYTHONDONTWRITEBYTECODE=1 \ - PIP_DEFAULT_TIMEOUT=100 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PIP_NO_CACHE_DIR=1 -ENV PATH="${PATH}:/root/.local/bin" - -RUN mkdir /src -RUN mkdir /static +# --------------------------------------------------------------------------- +# Frontend build stage +# --------------------------------------------------------------------------- +FROM node:22-slim AS frontend WORKDIR /src +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci +COPY frontend/ ./ +RUN npm run build -RUN echo "deb http://ftp.uk.debian.org/debian trixie non-free non-free-firmware" > /etc/apt/sources.list.d/non-free.list +# --------------------------------------------------------------------------- +# Runtime stage +# --------------------------------------------------------------------------- +FROM python:3.14-slim-trixie -RUN apt update \ - && apt install -y npm cron unrar libmariadb-dev libpq-dev pkg-config swig curl +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -COPY . /src/ +ENV PYTHONFAULTHANDLER=1 \ + PYTHONHASHSEED=random \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 +ENV UV_PROJECT_ENVIRONMENT=/opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# unrar lives in the non-free component; resolve the codename from the base image +# instead of hardcoding it. +RUN . /etc/os-release \ + && echo "deb http://deb.debian.org/debian ${VERSION_CODENAME} non-free non-free-firmware" \ + > /etc/apt/sources.list.d/non-free.list \ + && apt update \ + && apt install -y --no-install-recommends cron unrar libmariadb-dev pkg-config \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src + +# Lockfiles first so dependency installation is cached independently of the source. +COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev -RUN cd frontend \ - && npm install \ - && npm run build \ - && rm -r node_modules +COPY . /src/ +COPY --from=frontend /src/dist /src/frontend/dist +COPY --from=frontend /src/webpack-stats.json /src/frontend/webpack-stats.json +RUN mkdir -p /static /media RUN cat /src/cbreader/crontab >> /etc/crontab EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/')" || exit 1 + +CMD ["sh", "entrypoint.sh"] diff --git a/cbreader/crontab b/cbreader/crontab index 7ef4832..b41db45 100644 --- a/cbreader/crontab +++ b/cbreader/crontab @@ -1 +1 @@ -*/15 * * * * root /src/.venv/bin/python /src/manage.py scan_comics --settings=cbreader.settings.base +*/15 * * * * root /opt/venv/bin/python /src/manage.py scan_comics --settings=cbreader.settings.base diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 76957e6..af4bedf 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -1,64 +1,69 @@ -version: "2.4" - services: cbwebreader: + build: ../ image: ajurna/cbwebreader env_file: .env - links: - - database depends_on: database: condition: service_healthy expose: - 8000 volumes: - - ${COMIC_BOOK_VOLUME}:${COMIC_BOOK_VOLUME} + # COMIC_BOOK_VOLUME is the path *inside* the container; the host path is + # a separate variable so a Windows host path is not passed to Django. + - ${COMIC_BOOK_HOST_PATH}:${COMIC_BOOK_VOLUME} - static_files:/static - media_files:/media - .env:/src/.env - command: /bin/bash entrypoint.sh + command: sh entrypoint.sh + restart: unless-stopped cbwebreader-cron: + build: ../ image: ajurna/cbwebreader env_file: .env - links: - - database depends_on: database: condition: service_healthy volumes: - - ${COMIC_BOOK_VOLUME}:${COMIC_BOOK_VOLUME} + - ${COMIC_BOOK_HOST_PATH}:${COMIC_BOOK_VOLUME} - media_files:/media - .env:/src/.env - command: /bin/bash entrypoint-cron.sh + command: sh entrypoint-cron.sh + restart: unless-stopped database: - image: postgres:18-alpine + image: postgres:16-alpine expose: - 5432 volumes: - - ./data:/var/lib/postgresql/data + - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U $DB_USER -d $DB_DATABASE"] + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s timeout: 10s retries: 3 environment: - - POSTGRES_USER=${DB_USER} - - POSTGRES_PASSWORD=${DB_PASS} - - POSTGRES_DB=${DB_DATABASE} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASS} + POSTGRES_DB: ${DB_DATABASE} + restart: unless-stopped nginx: - image: nginx + image: nginx:1-alpine volumes: - static_files:/static - media_files:/media - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - - 1337:80 + - "${WEB_PORT:-8337}:80" depends_on: - - cbwebreader + cbwebreader: + condition: service_healthy + restart: unless-stopped + volumes: static_files: media_files: + postgres_data: diff --git a/docker-compose.yml b/docker-compose.yml index 31e6c59..d194a30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,65 +1,69 @@ -version: "2.4" - services: cbwebreader: build: . + image: ajurna/cbwebreader env_file: .env - links: - - database depends_on: database: condition: service_healthy expose: - 8000 volumes: -# - ${COMIC_BOOK_VOLUME}:/comics - - c:/comics:/comics + # COMIC_BOOK_VOLUME is the path *inside* the container; the host path is + # a separate variable so a Windows host path is not passed to Django. + - ${COMIC_BOOK_HOST_PATH}:${COMIC_BOOK_VOLUME} - static_files:/static - media_files:/media - .env:/src/.env - command: /bin/bash /src/entrypoint.sh + command: sh entrypoint.sh + restart: unless-stopped -# cbwebreader-cron: -# build: . -# env_file: .env -# links: -# - database -# depends_on: -# database: -# condition: service_healthy -# volumes: -# - ${COMIC_BOOK_VOLUME}:${COMIC_BOOK_VOLUME} -# - media_files:/media -# - .env:/src/.env -# command: /bin/bash entrypoint-cron.sh + cbwebreader-cron: + build: . + image: ajurna/cbwebreader + env_file: .env + depends_on: + database: + condition: service_healthy + volumes: + - ${COMIC_BOOK_HOST_PATH}:${COMIC_BOOK_VOLUME} + - media_files:/media + - .env:/src/.env + command: sh entrypoint-cron.sh + restart: unless-stopped database: image: postgres:16-alpine expose: - 5432 volumes: - - /var/lib/postgresql/data + - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U $DB_USER -d $DB_DATABASE"] + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s timeout: 10s retries: 3 environment: - - POSTGRES_USER=${DB_USER} - - POSTGRES_PASSWORD=${DB_PASS} - - POSTGRES_DB=${DB_DATABASE} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASS} + POSTGRES_DB: ${DB_DATABASE} + restart: unless-stopped nginx: - image: nginx + image: nginx:1-alpine volumes: - static_files:/static - media_files:/media - ./cbreader/settings/nginx.conf:/etc/nginx/conf.d/default.conf ports: - - 8337:80 + - "${WEB_PORT:-8337}:80" depends_on: - - cbwebreader + cbwebreader: + condition: service_healthy + restart: unless-stopped + volumes: static_files: media_files: + postgres_data: diff --git a/entrypoint.sh b/entrypoint.sh index b15a366..ab3fc11 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,8 @@ #!/usr/bin/env sh -uv run manage.py migrate --settings=cbreader.settings.base +set -e -uv run manage.py collectstatic --settings=cbreader.settings.base --noinput --clear +python manage.py migrate --settings=cbreader.settings.base -uv run gunicorn --workers 3 --bind 0.0.0.0:8000 cbreader.wsgi:application +python manage.py collectstatic --settings=cbreader.settings.base --noinput --clear + +exec gunicorn --workers 3 --bind 0.0.0.0:8000 cbreader.wsgi:application