Those like me who use Calibre to manage their ereader library are familiar with the hassle of syncing between the two. Standard choices are to either 1) physically hook up your ereader to the computer where Calibre is installed, or 2) access Calibre server or a similar service over your local network. I am not a fan of the former, and haven’t found an easy sync method with the latter. Recently I came across the web app Calibre-web, which offers a great online alternative along with a polished UI for online ebook management.
Calibre-web is python-based, and like most python applications, should be installed in an isolated environment to avoid issues with pre-existing dependencies. This can be achieved with either a virtual environment or a docker container. I have used both, and on balance find docker to be easier to deploy. This is especially true because a pre-built docker image is already available, and a good how-to on running it on your machine.
Here I’ll describe how to build your own docker image for Calibre-web. The main reason for this exercise is that it is possible to greatly reduce the image size by using a multi-stage Dockerfile
. Besides, working with docker is always a good practice. To follow along, you need Docker installed on your machine. I prefer working with terminals, and use docker CLI (command line interface) on a linux platform. You may instead prefer installing docker desktop that offers a simpler interface.
In the first step, I created a working directory ~/Documents/CalibreWeb
, moved into it, and installed the two packages calibreweb
and jsonschema
(jsonschema
is optional and needed for syncing with Kobo ereader) using pipenv
in a python virtual environment to generate the requirements.txt
file, with the following commands on a terminal:
> mkdir ~/Documents/CalibreWeb
> cd ~/Documents/CalibreWeb
> pipenv install calibreweb jsonschema
> pipenv run pip freeze > requirements.txt
A requirements.txt
file is strictly not necessary; instead one can directly install the two packages with Dockerfile
. The benefit of requirements.txt
is that the versions of these packages and all dependencies are frozen, which makes the docker image stable against future package upgrades, and also portable so everyone will install the same versions.
Single-stage dockerfile
Next step is creating the Dockerfile
. First a simple Dockerfile
that has just one stage: it starts with one base image and creates one final image.
# get latest stable python image
FROM python:3.10.7-bullseye
# set working directory (inside container)
WORKDIR /app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# create virtual environment (inside container)
RUN python -m venv .venv
ENV PATH="/app/.venv/bin:$PATH"
# install packages (including dependencies)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# run command
CMD ["cps"]
The code is simple enough: it pulls the latest stable python image available as of now, creates a working directory /app
and a python virtual environment in the container, appends the bin
folder of venv
to the start of the path (which is what activating venv
does), copies requirements.txt
from host to container, installs packages and dependencies, and runs the executable cps
(from /app/.venv/bin
). I also assigned the ENV
variable PYTHONDONTWRITEBYTECODE
to prevent python from writing out pyc
files, and PYTHONUNBUFFERED
to prevent buffering stdin/stdout.
Now let’s build the image and check its size:
> docker build -t calibreweb-run .
>
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
calibreweb-run latest d3b4e56b43c1 7 seconds ago 1.06GB
python 3.10.7-bullseye da84e66c3a7c 13 days ago 921MB
The image calibreweb-run is 1.06GB, hardly a surprise given that the base image itself is 921MB.
Multi-stage dockerfile
This is where a multi-stage Dockerfile
becomes a better alternative. Let’s see this with a two-stage Dockerfile
: a build stage using the same python base image as before, followed by a run stage using a much smaller python image.
# build stage
FROM python:3.10.7-bullseye AS build
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN python -m venv .venv
ENV PATH="/app/.venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# run stage
FROM python:3.10.7-alpine3.16
WORKDIR /app
COPY --from=build /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
CMD ["cps"]
The build stage is nearly identical to the earlier Dockerfile
(except the CMD
instruction is now in the run stage). This is where all the python packages and dependencies are installed. The run stage pulls the latest stable python:alpine image as its base image (typically smallest of the available python images), creates the same working directory /app
in this new container, copies the python environment including all installed packages from the build stage, appends to path as before, and executes cps
.
Let’s build the image by running docker build
twice, and check image sizes with docker images
:
> docker build -t calibreweb-build --target build .
> docker build -t calibreweb-run .
>
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
calibreweb-run latest 4de0c7777063 4 seconds ago 181MB
calibreweb-build latest 9d71247e3e28 40 seconds ago 1.06GB
python 3.10.7-alpine3.16 2bbcf2f78d64 11 days ago 48.7MB
python 3.10.7-bullseye da84e66c3a7c 13 days ago 921MB
First command targets the build stage and generates the image tagged calibreweb-build, and the second command creates the run-stage image calibreweb-run. First command is not necessary; the second command alone still creates the same two images, except that the build-stage image gets a default <none> tag. I prefer explicit tagging of all intermediate stages.
Now there is a tiny python:alpine image, together with the huge build-stage image as expected, and the new much leaner run-stage image calibreweb-run.
To create the container, I run the image calibreweb-run in detached mode (to get the shell back) with this script :
#!/usr/bin/env bash
docker run -d \
--name=calibreweb \
-p 8083:8083 \
-v $HOME/.calibre-web:/config \
-v $HOME/Documents/Calibre\ Library:/books \
calibreweb-run
The container port 8083 is mapped to the host port 8083 for web access (to the Calibre library) via http://<your IP address>:8083
, and the two host folders, $HOME/.calibre-web
for configuration settings and $HOME/Documents/Calibre Library
(where I store my library), are mounted to the container folders /config
and /books
via volume mapping. Once the container is up and running, Kobo sync can be set up by following the instructions given here and here.
Hi there! I am Roy, founder of Quantiux and author of everything you see here. I am a data scientist, with a deep interest in playing with data of all colors, shapes and sizes. I also enjoy coding anything that catches my fancy.
Leave a comment below if this article interests you, or contact me for any general comment/question.