feat: update cookiecutter template to support new package managers and asset bundlers - #304
Conversation
d4b4f3f to
a4dd787
Compare
max-moser
left a comment
There was a problem hiding this comment.
the Dockerfile still needs a few tweaks for uv and/or pnpm.
for reference, this is our current variant, with both enabled:
# Dockerfile that builds a fully functional image of your app.
FROM ghcr.io/astral-sh/uv:alpine AS builder
# the server name is just there to satisfy the strict startup sanity check
# it's not really used during the build step, so it can be set to anything
ARG INVENIO_SERVER_NAME=localhost
ARG INVENIO_INSTANCE_PATH=/var/instance
# set language/locale
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
ENV PATH="${INVENIO_INSTANCE_PATH}/.venv/bin:${PATH}"
# create the instance dir and set it as working directory
RUN mkdir -p "${INVENIO_INSTANCE_PATH}"
WORKDIR ${INVENIO_INSTANCE_PATH}
# install build dependencies
RUN apk update && \
apk add cairo gcc git linux-headers musl-dev nodejs npm py3-setuptools python3 python3-dev && \
npm install --global --ignore-scripts pnpm
# install the python dependencies system-wide
COPY pyproject.toml uv.lock ./
RUN uv sync --locked --no-progress --compile-bytecode && \
uv clean
# copy the relevant files from the local project directory
COPY ./docker/uwsgi/ ${INVENIO_INSTANCE_PATH}/uwsgi/
COPY ./invenio.cfg ${INVENIO_INSTANCE_PATH}/
COPY ./app_data/ ${INVENIO_INSTANCE_PATH}/app_data/
COPY ./assets/ /tmp/assets/
COPY ./static/ /tmp/static/
COPY ./templates/ /tmp/templates/
COPY ./translations/ /tmp/translations/
# collect & build the frontend, and clean up unnecessary source files
# local overrides are copied over before/after the build step so they don't get lost during the build
ENV INVENIO_WEBPACKEXT_NPM_PKG_CLS=pynpm:PNPMPackage
RUN invenio collect --verbose && \
mkdir assets templates translations && \
cp -r /tmp/assets/ ${INVENIO_INSTANCE_PATH}/ && \
invenio webpack buildall && \
cp -r /tmp/static/ ${INVENIO_INSTANCE_PATH}/ && \
cp -r /tmp/templates/ ${INVENIO_INSTANCE_PATH}/ && \
cp -r /tmp/translations/ ${INVENIO_INSTANCE_PATH}/ && \
rm -rf ${INVENIO_INSTANCE_PATH}/assets/node_modules && \
pnpm cache delete
# the actual invenio app image
FROM ghcr.io/astral-sh/uv:alpine
ARG INVENIO_INSTANCE_PATH=/var/instance
WORKDIR ${INVENIO_INSTANCE_PATH}
# set language/locale
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
ENV INVENIO_INSTANCE_PATH=/var/instance
ENV PATH="${INVENIO_INSTANCE_PATH}/.venv/bin:${PATH}"
# install the runtime dependencies
RUN apk update && \
apk add cairo font-dejavu imagemagick py3-setuptools python3 && \
apk cache clean
# copy over the built application
COPY --from=builder "${INVENIO_INSTANCE_PATH}" "${INVENIO_INSTANCE_PATH}"
RUN chmod g+w "${INVENIO_INSTANCE_PATH}"
ENTRYPOINT ["sh", "-c"]a85d0da to
d0691bb
Compare
Thanks for sharing your image @max-moser it was really helpful! 😊 I noticed there were some unnecessary packages being installed during both the build and runtime stages. For example, installing Python separately isn't needed if you're already starting from a Python base image. Regarding the copy steps you were copying into /tmp and then moving multiple folders. I felt it was simpler to use a single COPY . . command and manage exclusions through the .dockerignore file instead as the build time is fast I ignored the layers caching that we can benefit from multiple copy commands. Also for Node.js wasn't version-controlled, so I opted to copy it from a Node base image so we can control the version in the future. For the runtime stage, I could have started from a Python Alpine base image, which would’ve reduced the image size to around 1.20 GB. However, I chose to keep uv as base so I can manage packages directly on the pod if needed. With this setup, the final image size is 1.24 GB, and the build time stays under 4 minutes. This is the updated version let me know if we miss something here: Here is also an example for the pyproject.yaml file for both the instance and site package ARG JS_PACKAGE_MANAGER=pnpm@10.8.1
ARG NODE_IMAGE=node:22-alpine
ARG PYTHON_BASE_IMAGE=ghcr.io/astral-sh/uv:0.6-python3.12-alpine
ARG WORKING_DIR=/opt/invenio
ARG INVENIO_INSTANCE_PATH=${WORKING_DIR}/var/instance
# --- NODE.JS STAGE ---
FROM ${NODE_IMAGE} AS node
ARG JS_PACKAGE_MANAGER
ENV JS_PACKAGE_MANAGER=${JS_PACKAGE_MANAGER}
RUN corepack enable && corepack prepare ${JS_PACKAGE_MANAGER} --activate
# --- BASE SETUP STAGE ---
FROM ${PYTHON_BASE_IMAGE} AS python_base
ARG INVENIO_INSTANCE_PATH
ENV INVENIO_INSTANCE_PATH=${INVENIO_INSTANCE_PATH} \
LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8 \
# Compile Python files to .pyc bytecode files
UV_COMPILE_BYTECODE=1 \
# Copy Python files from cache mount, resolving symlink issues
UV_LINK_MODE=copy
ENV PATH="${INVENIO_INSTANCE_PATH}/.venv/bin:${PATH}"
RUN apk update && \
apk add --no-cache \
bash cairo \
imagemagick util-linux
# --- BUILD APP STAGE ---
FROM python_base AS builder
WORKDIR ${INVENIO_INSTANCE_PATH}
RUN apk add --no-cache \
gcc \
musl-dev \
linux-headers
# Copy Node.js runtime libraries and binaries
COPY --from=node /usr/lib /usr/lib
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib /usr/local/lib
COPY --from=node /usr/local/include /usr/local/include
COPY --from=node /usr/local/share /usr/local/share
# Count on .dockerignore to exclude files
COPY . .
# Sync Python dependencies
RUN uv sync --locked && \
uv cache clean
# --- FRONTEND BUILD ---
ENV INVENIO_WEBPACKEXT_NPM_PKG_CLS=pynpm:PNPMPackage
RUN uv run invenio collect --verbose && \
mkdir -p assets templates translations site data archive && \
uv run invenio webpack buildall && \
rm -rf assets/node_modules && \
# Experimental command!
# https://pnpm.io/cli/cache-delete
pnpm cache delete && \
rm -rf "$(pnpm store path)" && \
# Uwsgi config expected to be on instance level
cp -a docker/uwsgi/. .
# --- RUNTIME STAGE ---
FROM python_base AS runtime
RUN addgroup -S invenio && \
adduser -S -G invenio invenio
COPY --from=builder --chown=invenio:invenio \
"${INVENIO_INSTANCE_PATH}" \
"${INVENIO_INSTANCE_PATH}"
USER invenio
WORKDIR ${INVENIO_INSTANCE_PATH}
ENTRYPOINT ["bash", "-c"]
|
dbc0508 to
15803d4
Compare
|
After discussing with @slint and others during the teleconference call, here’s a summary of the Cookie-cutter strategy as I understand it:
In short, we'll stick to one option for the Dockerfile and avoid offering multiple choices. |
bbe55df to
be87580
Compare
|
@Samk13 Hi 👋 What's the current status on this pr? |
Hi @OliverGeneser 👋 The only open point is the Dockerfile base image. Apart from that, everything is ready. Once this is clarified, I can resolve the conflicts and squash the commits. |
|
Something to keep in mind is that Alpine uses For instance, some Python packages provide pre-built wheels for Some Go programs also didn't execute or even compile properly because of some incompatibility with We've switched over to Wolfi as a base, which is very similar to Alpine but e.g. uses Here's a few more insights about |
|
Thanks for sharing @max-moser, that’s a very good point 👍 We should bring this up again in the upcoming maintainers meeting and decide on the base image there. I’ll add it as an agenda point. |
cafce17 to
ae91205
Compare
1ba6bdd to
d3ea1d4
Compare
|
Update from maintainer summary on this change last week:
Alignment with the maintainer call agreement ( AI analysis) ✅ Cookiecutter simplification achieved ✅ Tooling defaults implemented
|
fc69ea2 to
1b3a12e
Compare
a0ed593 to
f53e26b
Compare
There was a problem hiding this comment.
Question: Should we add named volumes in the docker-*.yml files?
In my experience, that's always something of the first things that I add on a fresh setup, to make the setup less brittle.
E.g. have data survive across container rebuilds, which is a major point of annoyance for me personally whenever setting up a new instance.
There was a problem hiding this comment.
Updated here added named volumes for persistence across rebuilds.
Do you think we should also move the CHANGE_ME values in docker-services to env vars and add a .env.example?
There was a problem hiding this comment.
From my perspective, I would very much welcome fewer hard-coded secrets, be it through .env (which I generally use) or other mechanisms!
Would you suggest adding a .env file as part of the template, filled out with some default values?
That sounds good to me; the only thing that I could imagine is the files starting with . being hidden from ls by default.
That's why I've come to change the order in the naming, usually to example.env, e.g. with some explainer text; cf. KSTU setup
Perhaps we could find some inbetween way, like instantiating both .env and example.env, and adding .env to .gitignore?
What do you think?
I'm open for alternatives!
There was a problem hiding this comment.
I intentionally avoided adding broader app-level variables to example.env at this stage, except INVENIO_SECRET_KEY, because otherwise we start mixing two concerns service/container and general Invenio application.
WDYT @fenekku @max-moser?
c9537ab (this PR)
There was a problem hiding this comment.
Personally, I'm fine with adding all kinds of configuration in .env; at TUW we've been doing that for a good while already with the env_file: .env property.
Admittedly it's true that our .env files are quite messy with all sorts of config in there, but so far this hasn't been a huge issue for me...
At least for us, the benefit of simplicity outweighs the downside of mixed concerns.
If splitting service config from app config is a concern, would it be feasible to have two different config files (that are excluded from version control), e.g. .invenio-env and .containers-env, or perhaps even an invenio-private.cfg (a Python file which gets loaded by invenio.cfg if it exists [1]), along with a .env for services?
[1] Should of course not be built into the container images upon build but only mounted; otherwise anybody with access to the image gets to see the secrets. Getting access to images is of course generally much easier than getting access to the running containers.
There was a problem hiding this comment.
I think I'd still lean towards a single .env eventually, but since this PR is primarily about upgrading the cookiecutter, I'd prefer to keep the current separation for now.
Introducing a new configuration approach (single .env, multiple env files, loading additional config files, etc.) is a broader change that deserves its own discussion and PR.
That would let us evaluate the trade-offs independently of the cookiecutter upgrade and keep the scope of this PR "focused".
We already have enough changes here that make the PR intimidating enough to review as it is 😅.
As there's interest, I'd revert this change and open a follow-up PR to consolidate the configuration and move the remaining CHANGE_ME values into a single .env if that's okay with you.
There was a problem hiding this comment.
I moved the .env changes here: #333
We need to merge this one first.
There was a problem hiding this comment.
Agreed, splitting that part out of this PR is probably best 👍
f53e26b to
5a69f9f
Compare
5f95430 to
4be60d0
Compare
fenekku
left a comment
There was a problem hiding this comment.
Thank you for all these updates! There are obviously work outside of this PR that needs to be done before it can be used. I really appreciate how it cleans up a lot of cruft and drops technologies not part of the guaranteed happy path.
| requires = ["setuptools", "wheel", "babel>2.8"] | ||
| build-backend = "setuptools.build_meta" | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" |
There was a problem hiding this comment.
Let's keep setuptools for now I would say since this is what is used by every other Invenio(RDM) package. We can have a maintainer meeting to discuss build systems if people have strong opinions.
There was a problem hiding this comment.
I added Hatchling mainly to align with the Zenodo setup, which has already moved to.
That said, I don't have a strong opinion on the build backend itself.
There was a problem hiding this comment.
I thought the zenodo switch was a test-it-out situation. Rather we keep with setuptools but it's not a blocker for me then. I'd be curious to hear the arguments for hatchling.
| "yes", | ||
| "no" |
There was a problem hiding this comment.
This would change defaults. I don't have a strong opinion, but worth making it clear to other reviewers.
There was a problem hiding this comment.
Yeah, for other reviewers, this intentionally changes the old default.
This cookiecutter is mostly used for development and demos, where you typically want a running instance fast without long time indexing. I found myself selecting yes way more often than not, and occasionally missing the prompt and having to start over, which is why I changed this default.
| uv run invenio index destroy --force --yes-i-know | ||
| uv run invenio index init --force | ||
| uv run invenio index queue init purge | ||
| if [ $COOKIECUTTER_FILE_STORAGE -eq "S3" ] |
There was a problem hiding this comment.
eq is numeric comparison in POSIX shell. this variable is a string
c9537ab to
2bfc76e
Compare
|
With OR2026 I haven't had the time to look in docker-invenio hosting, but will do so next week ( 🤞 ) |
* Update cookiecutter template for modern Python workflows * Switch tooling to uv workspaces and hatchling backend * Simplify package manager and configuration options * Refactor Dockerfiles for flexibility, caching, and clarity * Remove deprecated JS, database, and search options * Align project metadata, versions, and naming conventions * Update Docker images and clean up documentation
* Introduced base_image field with options for "debian" and "alpine" in cookiecutter.json.
* Added support for pnpm installation and configuration. * Updated Dockerfile to include necessary dependencies for uv. * Improved directory structure and permissions for application.
* Simplified base image selection by removing alpine option. * Ensures consistency in Dockerfile usage with debian base image.
* this is needed to run in worker pod when using: docker compose -f docker-compose.full.yml up -d --build
* modify run-tests.sh to use updated uv command * change bootstrap script to install project with test extras * update pyproject.toml for app version and dependencies * adjust site pyproject.toml for versioning and requirements
* Refactor project name variables for consistency * Use project_shortname for instance and distribution names
* Updated docker-compose and docker-services files to include volumes for db_data, mq_data, and search_data. * Adjusted S3 storage handling in the configuration.
* Introduced INVENIO_WEBPACKEXT_NPM_PKG_CLS * to support pynpm:PNPMPackage in Dockerfile
* Adjust uv.lock path handling in bootstrap script * Update Dockerfile to include new ARG for npm package class * Modify docker-services.yml to use updated postgres image * Add optional dependencies for testing in pyproject.toml * Remove deprecated setup.cfg and setup.py files * Clean up __init__.py by removing version definition
* assert proper handling of variables in bash scripts.
* Cleaned up the pyproject.toml by removing the optional dependencies section for tests. * test installed with uv sync --extra tests
* Adjusted the FROM instruction to remove the alias for base image. * Added comments to clarify ARG scoping in Dockerfile.
* fix Conflicts: npm after moving the arg NODE_VERSION inder from
2bfc76e to
860252f
Compare
* Add health checks for services in docker-compose * Update Dockerfile copyright format * Set environment variables for UI and API URLs
* Bump opensearch to version 2.19.5 * Bump opensearch-dashboards to version 2.19.5
|
I didn't have time to investigate the docker hosting on the docker-invenio repo directly and won't until next week, but this LGTM and is better merged in to get more people to test it out. So merging :) . |
❤️ Thank you for your contribution!
Description
For:
inveniosoftware/invenio-cli#385
inveniosoftware/invenio-cli#384
Needs:
inveniosoftware/invenio-cli#392
Checklist
Ticks in all boxes and 🟢 on all GitHub actions status checks are required to merge:
Frontend
Reminder
By using GitHub, you have already agreed to the GitHub’s Terms of Service including that: