# syntax=docker/dockerfile:1 # Simple Example Build Command: # docker build \ # --tag crocodilestick/calibre-web-automated:dev \ # --build-arg="BUILD_DATE=27-09-2024 12:06" \ # --build-arg="VERSION=2.1.0-test-5" . # Good guide on how to set up a buildx builder here: # https://a-berahman.medium.com/simplifying-docker-multiplatform-builds-with-buildx-3d7efd670f58 # Multi-Platform Example Build & Push Command: # docker buildx build \ # --push \ # --platform linux/amd64,linux/arm64, \ # --build-arg="BUILD_DATE=02-08-2024 20:52" \ # --build-arg="VERSION=2.1.0" \ # --tag crocodilestick/calibre-web-automated:latest . # ========================================================================== # STAGE 1: Dependencies - Install system packages and Python dependencies # ========================================================================== ARG CALIBRE_RELEASE=9.1.0 ARG KEPUBIFY_RELEASE=v4.0.4 FROM ghcr.io/linuxserver/baseimage-ubuntu:noble AS dependencies ARG CALIBRE_RELEASE ARG KEPUBIFY_RELEASE # Set the default shell for the following RUN instructions to bash instead of sh SHELL ["/bin/bash", "-c"] # STEP 1 - Install Required Packages RUN \ # STEP 1.1 - Add deadsnakes PPA for Python 3.13 and install required apt packages echo "**** add deadsnakes PPA for Python 3.13 ****" && \ apt-get update && \ apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository ppa:deadsnakes/ppa && \ apt-get update && \ echo "**** install build packages ****" && \ apt-get install -y --no-install-recommends \ build-essential \ libldap2-dev \ libsasl2-dev \ gettext \ python3.13-dev \ python3.13-venv \ curl && \ echo "**** install runtime packages ****" && \ apt-get install -y --no-install-recommends \ imagemagick \ ghostscript \ libldap2 \ libmagic1 \ libsasl2-2 \ libxi6 \ libxslt1.1 \ xdg-utils \ inotify-tools \ python3.13 \ nano \ sqlite3 \ zip && \ # STEP 1.2 - Install additional Calibre required packages apt-get install -y --no-install-recommends \ libxtst6 \ libxrandr2 \ libxkbfile1 \ libxcomposite1 \ libxcursor1 \ libxfixes3 \ libxrender1 \ libopengl0 \ libnss3 \ libxkbcommon0 \ libegl1 \ libxdamage1 \ libgl1 \ libglx-mesa0 \ xz-utils \ binutils && \ # Install lsof 4.99.5 from source to fix hanging issue with 4.95 (issue #654) echo "**** install lsof 4.99.5 from source ****" && \ LSOF_VERSION="4.99.5" && \ curl -L "https://github.com/lsof-org/lsof/archive/${LSOF_VERSION}.tar.gz" -o /tmp/lsof.tar.gz && \ cd /tmp && \ tar -xzf lsof.tar.gz && \ cd "lsof-${LSOF_VERSION}" && \ ./Configure -n linux && \ make && \ cp lsof /usr/bin/lsof && \ chmod 755 /usr/bin/lsof && \ cd / && \ rm -rf /tmp/lsof* && \ # Create python3 symlink to point to python3.13 ln -sf /usr/bin/python3.13 /usr/bin/python3 && \ # Install pip for Python 3.13 curl -sS https://bootstrap.pypa.io/get-pip.py | python3.13 # STEP 2 - Set up Python virtual environment RUN \ python3.13 -m venv /lsiopy && \ /lsiopy/bin/pip install -U --no-cache-dir \ pip \ wheel # STEP 3 - Copy requirements files and install Python packages # Copy only requirements files first to leverage Docker layer caching COPY --chown=abc:abc requirements.txt optional-requirements.txt /app/calibre-web-automated/ RUN \ # STEP 3.1 - Installing the required python packages listed in 'requirements.txt' and 'optional-requirements.txt' # HOWEVER, they are not pulled from PyPi directly, they are pulled from linuxserver's Ubuntu Wheel Index # This is essentially a repository of precompiled some of the most popular packages with C/C++ source code # This provides the install maximum compatibility with multiple different architectures including: x86_64, armv71 and aarch64 # You can read more about python wheels here: https://realpython.com/python-wheels/ /lsiopy/bin/pip install -U --no-cache-dir --find-links https://wheel-index.linuxserver.io/ubuntu/ -r \ /app/calibre-web-automated/requirements.txt -r /app/calibre-web-automated/optional-requirements.txt # STEP 4 - Install kepubify RUN \ echo "**** install kepubify ****" && \ if [[ $KEPUBIFY_RELEASE == 'newest' ]]; then \ KEPUBIFY_RELEASE=$(curl -sX GET "https://api.github.com/repos/pgaskin/kepubify/releases/latest" \ | awk '/tag_name/{print $4;exit}' FS='[""]'); \ fi && \ if [ "$(uname -m)" == "x86_64" ]; then \ curl -o \ /usr/bin/kepubify -L \ https://github.com/pgaskin/kepubify/releases/download/${KEPUBIFY_RELEASE}/kepubify-linux-64bit; \ elif [ "$(uname -m)" == "aarch64" ]; then \ curl -o \ /usr/bin/kepubify -L \ https://github.com/pgaskin/kepubify/releases/download/${KEPUBIFY_RELEASE}/kepubify-linux-arm64; \ fi && \ chmod +x /usr/bin/kepubify # STEP 5 - Install Calibre RUN \ # STEP 5.1 - Make the /app/calibre directory for the installed files mkdir -p /app/calibre && \ # STEP 5.2 - Download the desired version of Calibre, determined by the CALIBRE_RELEASE variable and the architecture of the build environment if [ "$(uname -m)" == "x86_64" ]; then \ curl -o \ /calibre.txz -L \ "https://download.calibre-ebook.com/${CALIBRE_RELEASE}/calibre-${CALIBRE_RELEASE}-x86_64.txz"; \ elif [ "$(uname -m)" == "aarch64" ]; then \ curl -o \ /calibre.txz -L \ "https://download.calibre-ebook.com/${CALIBRE_RELEASE}/calibre-${CALIBRE_RELEASE}-arm64.txz"; \ fi && \ # STEP 5.3 - Extract the downloaded file to /app/calibre tar xf \ /calibre.txz -C \ /app/calibre && \ # STEP 5.3.1 - Remove the ABI tag from the extracted libQt6* files to allow them to be used on older kernels # Removed in V3.1.4 because it was breaking Calibre features that require Qt6. Replaced with a kernel check in the cwa-init service # STEP 5.4 - Delete the extracted calibre.txz to save space in final image rm /calibre.txz # ============================================================================ # STAGE 2: Final - Build the final runtime image # ============================================================================ FROM ghcr.io/linuxserver/baseimage-ubuntu:noble AS unrar-stage FROM ghcr.io/linuxserver/unrar:latest AS unrar FROM ghcr.io/linuxserver/baseimage-ubuntu:noble ARG BUILD_DATE ARG VERSION ARG CALIBRE_RELEASE ARG KEPUBIFY_RELEASE LABEL build_version="Version:- ${VERSION}" LABEL build_date="${BUILD_DATE}" LABEL maintainer="CrocodileStick" # Set the default shell for the following RUN instructions to bash instead of sh SHELL ["/bin/bash", "-c"] # Copy installed dependencies from the dependencies stage COPY --from=dependencies /lsiopy /lsiopy COPY --from=dependencies /usr/bin/kepubify /usr/bin/kepubify COPY --from=dependencies /app/calibre /app/calibre COPY --from=dependencies /usr/bin/lsof /usr/bin/lsof COPY --from=dependencies /usr/bin/python3.13 /usr/bin/python3.13 COPY --from=dependencies /usr/lib/python3.13 /usr/lib/python3.13 # Install only runtime packages (no build tools) RUN \ echo "**** add deadsnakes PPA for Python 3.13 runtime ****" && \ apt-get update && \ apt-get install -y --no-install-recommends software-properties-common && \ add-apt-repository ppa:deadsnakes/ppa && \ apt-get update && \ echo "**** install runtime packages ****" && \ apt-get install -y --no-install-recommends \ imagemagick \ ghostscript \ libldap2 \ libmagic1 \ libsasl2-2 \ libxi6 \ libxslt1.1 \ xdg-utils \ inotify-tools \ python3.13 \ nano \ sqlite3 \ zip \ gettext \ libasound2t64 \ libxtst6 \ libxrandr2 \ libxkbfile1 \ libxcomposite1 \ libxcursor1 \ libxfixes3 \ libxrender1 \ libopengl0 \ libnss3 \ libxkbcommon0 \ libegl1 \ libxdamage1 \ libgl1 \ libglx-mesa0 \ xz-utils \ curl && \ # Create python3 symlink to point to python3.13 ln -sf /usr/bin/python3.13 /usr/bin/python3 && \ # Cleanup apt-get -y purge software-properties-common && \ apt-get -y autoremove && \ rm -rf \ /tmp/* \ /var/lib/apt/lists/* \ /var/tmp/* \ /root/.cache # STEP 6 - Copy application files # Copy the rest of the application code (changes most frequently) COPY --chown=abc:abc . /app/calibre-web-automated/ # STEP 7 - Configure application RUN \ # STEP 7.1 - Move contents of /app/calibre-web-automated/root to / and delete the /app/calibre-web-automated/root directory cp -R /app/calibre-web-automated/root/* / && \ rm -R /app/calibre-web-automated/root/ && \ # STEP 7.2 - Run CWA install script to make required dirs, set script permissions and add aliases for CLI commands ect. chmod +x /app/calibre-web-automated/scripts/setup-cwa.sh && \ /app/calibre-web-automated/scripts/setup-cwa.sh && \ # STEP 7.3 - Create koplugin.zip from KOReader plugin folder echo "~~~~ Creating koplugin.zip from KOReader plugin folder... ~~~~" && \ if [ -d "/app/calibre-web-automated/koreader/plugins/cwasync.koplugin" ]; then \ cd /app/calibre-web-automated/koreader/plugins && \ # Calculate digest of all files in the plugin for debugging purposes echo "Calculating digest of plugin files..." && \ PLUGIN_DIGEST=$(find cwasync.koplugin -type f -name "*.lua" -o -name "*.json" | sort | xargs sha256sum | sha256sum | cut -d' ' -f1) && \ echo "Plugin digest: $PLUGIN_DIGEST" && \ # Create a file named after the digest inside the plugin folder echo "Plugin files digest: $PLUGIN_DIGEST" > cwasync.koplugin/${PLUGIN_DIGEST}.digest && \ echo "Build date: $(date)" >> cwasync.koplugin/${PLUGIN_DIGEST}.digest && \ echo "Files included:" >> cwasync.koplugin/${PLUGIN_DIGEST}.digest && \ find cwasync.koplugin -type f -name "*.lua" -o -name "*.json" | sort >> cwasync.koplugin/${PLUGIN_DIGEST}.digest && \ zip -r koplugin.zip cwasync.koplugin/ && \ echo "Created koplugin.zip from cwasync.koplugin folder with digest file: ${PLUGIN_DIGEST}.digest"; \ else \ echo "Warning: cwasync.koplugin folder not found, skipping zip creation"; \ fi && \ # STEP 7.4 - Move koplugin.zip to static directory if [ -f "/app/calibre-web-automated/koreader/plugins/koplugin.zip" ]; then \ mkdir -p /app/calibre-web-automated/cps/static && \ cp /app/calibre-web-automated/koreader/plugins/koplugin.zip /app/calibre-web-automated/cps/static/ && \ echo "Moved koplugin.zip to static directory"; \ else \ echo "Warning: koplugin.zip not found, skipping move to static directory"; \ fi && \ # STEP 7.5 - ADD files referencing the versions of the installed main packages echo "$VERSION" >| /app/CWA_RELEASE && \ echo "$KEPUBIFY_RELEASE" >| /app/KEPUBIFY_RELEASE && \ echo "$CALIBRE_RELEASE" > /CALIBRE_RELEASE # Add unrar from unrar stage COPY --from=unrar /usr/bin/unrar-ubuntu /usr/bin/unrar # Set calibre environment variable ENV CALIBRE_CONFIG_DIR=/config/.config/calibre # Ports and volumes WORKDIR /config # The default port CWA listens on. Can be overridden with the CWA_PORT_OVERRIDE environment variable. EXPOSE 8083 VOLUME /config VOLUME /cwa-book-ingest VOLUME /calibre-library # Health check for container orchestration # Uses shell form to support environment variable substitution for CWA_PORT_OVERRIDE # -L follows redirects so the 302 to /login on the root path is treated as healthy. # curl gets its own time limits so a hung web server fails the check cleanly instead of # Docker killing curl at --timeout and leaving the connection open (from #1335). HEALTHCHECK --interval=30s --timeout=3s --start-period=120s --retries=3 \ CMD curl -fsL --connect-timeout 1 --max-time 2 http://localhost:${CWA_PORT_OVERRIDE:-8083}/ || curl -fsL -k --connect-timeout 1 --max-time 2 https://localhost:${CWA_PORT_OVERRIDE:-8083}/ || exit 1