FROM node:22-alpine AS node-build WORKDIR /etc/linkding # install build dependencies COPY rollup.config.mjs postcss.config.js package.json package-lock.json ./ RUN npm ci # copy files needed for JS build COPY bookmarks/frontend ./bookmarks/frontend COPY bookmarks/styles ./bookmarks/styles # run build RUN npm run build FROM python:3.13.7-alpine3.22 AS build-deps # Add required packages # alpine-sdk linux-headers pkgconfig: build Python packages from source # libpq-dev: build Postgres client from source # icu-dev sqlite-dev: build Sqlite ICU extension # libffi-dev openssl-dev rust cargo: build Python cryptography from source RUN apk update && apk add alpine-sdk linux-headers libpq-dev pkgconfig icu-dev sqlite-dev libffi-dev openssl-dev rust cargo WORKDIR /etc/linkding # install uv, use installer script for now as distroless images are not availabe for armv7 ADD https://astral.sh/uv/0.8.13/install.sh /uv-installer.sh RUN chmod +x /uv-installer.sh && /uv-installer.sh # install python dependencies COPY pyproject.toml uv.lock ./ RUN /root/.local/bin/uv sync --no-dev --group postgres FROM build-deps AS compile-icu # Defines SQLite version # Since this is only needed for downloading the header files this probably # doesn't need to be up-to-date, assuming the SQLite APIs used by the ICU # extension do not change ARG SQLITE_RELEASE_YEAR=2023 ARG SQLITE_RELEASE=3430000 # Compile the ICU extension needed for case-insensitive search and ordering # with SQLite. This does: # - Download SQLite amalgamation for header files # - Download ICU extension source file # - Compile ICU extension RUN wget https://www.sqlite.org/${SQLITE_RELEASE_YEAR}/sqlite-amalgamation-${SQLITE_RELEASE}.zip && \ unzip sqlite-amalgamation-${SQLITE_RELEASE}.zip && \ cp sqlite-amalgamation-${SQLITE_RELEASE}/sqlite3.h ./sqlite3.h && \ cp sqlite-amalgamation-${SQLITE_RELEASE}/sqlite3ext.h ./sqlite3ext.h && \ wget https://www.sqlite.org/src/raw/ext/icu/icu.c?name=91c021c7e3e8bbba286960810fa303295c622e323567b2e6def4ce58e4466e60 -O icu.c && \ gcc -fPIC -shared icu.c `pkg-config --libs --cflags icu-uc icu-io` -o libicu.so FROM python:3.13.7-alpine3.22 AS linkding LABEL org.opencontainers.image.source="https://github.com/sissbruecker/linkding" # install runtime dependencies RUN apk update && apk add bash curl icu libpq mailcap libssl3 # create www-data user and group RUN set -x ; \ addgroup -g 82 -S www-data ; \ adduser -u 82 -D -S -G www-data www-data && exit 0 ; exit 1 WORKDIR /etc/linkding # copy python dependencies COPY --from=build-deps /etc/linkding/.venv /etc/linkding/.venv # copy output from node build COPY --from=node-build /etc/linkding/bookmarks/static bookmarks/static/ # copy compiled icu extension COPY --from=compile-icu /etc/linkding/libicu.so libicu.so # copy application code COPY . . # Activate virtual env ENV VIRTUAL_ENV=/etc/linkding/.venv ENV PATH="/etc/linkding/.venv/bin:$PATH" # Generate static files, remove source styles that are not needed RUN mkdir data && \ python manage.py collectstatic # Limit file descriptors used by uwsgi, see https://github.com/sissbruecker/linkding/issues/453 ENV UWSGI_MAX_FD=4096 # Expose uwsgi server at port 9090 EXPOSE 9090 # Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman RUN chmod g+w . && \ chmod +x ./bootstrap.sh HEALTHCHECK --interval=30s --retries=3 --timeout=1s \ CMD curl -f http://localhost:${LD_SERVER_PORT:-9090}/${LD_CONTEXT_PATH}health || exit 1 CMD ["./bootstrap.sh"] FROM node:22-alpine AS ublock-build WORKDIR /etc/linkding # Install necessary tools # Download and unzip the latest uBlock Origin Lite release # Patch manifest to enable annoyances by default # Patch ruleset-manager.js to use rulesets enabled in manifest by default RUN apk add --no-cache curl jq unzip && \ TAG=$(curl -sL https://api.github.com/repos/uBlockOrigin/uBOL-home/releases/latest | jq -r '.tag_name') && \ DOWNLOAD_URL=https://github.com/uBlockOrigin/uBOL-home/releases/download/$TAG/$TAG.chromium.mv3.zip && \ echo "Downloading $DOWNLOAD_URL" && \ curl -L -o uBOLite.zip $DOWNLOAD_URL && \ unzip uBOLite.zip -d uBOLite.chromium.mv3 && \ rm uBOLite.zip && \ jq '.declarative_net_request.rule_resources |= map(if .id == "annoyances-overlays" or .id == "annoyances-cookies" or .id == "annoyances-social" or .id == "annoyances-widgets" or .id == "annoyances-others" then .enabled = true else . end)' \ uBOLite.chromium.mv3/manifest.json > temp.json && \ mv temp.json uBOLite.chromium.mv3/manifest.json FROM linkding AS linkding-plus # install node, chromium RUN apk update && apk add nodejs npm chromium-swiftshader # install single-file-cli RUN npm install -g single-file-cli@2.0.75 # copy uBlock COPY --from=ublock-build /etc/linkding/uBOLite.chromium.mv3 uBOLite.chromium.mv3/ # create chromium profile folder for user running background tasks and set permissions RUN mkdir -p chromium-profile && \ chown -R www-data:www-data chromium-profile && \ chown -R www-data:www-data uBOLite.chromium.mv3 # enable snapshot support ENV LD_ENABLE_SNAPSHOTS=True