-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
poetry install --no-root
with only poetry.lock
#1301
Comments
In the preview version there is an export command to have poetry generate the requirements.txt; I’ve been using that in my own projects (and otherwise following almost exactly the same steps you described). That said, I would also prefer to make use of this feature if it existed. |
Honest question, why would you use poetry to set up a container? pip (with |
I'm using poetry in a docker container because of subtle bugs like #1129. I probably could work around it by not having optional git dependencies or by creating an internal package repo and building/publishing packages, or manually editing the broken METADATA file, but it's a lot easier to just install the couple of internal dependencies straight out of our git hosting by using poetry instead of pip. Silly, but effective, and since my container is a multibuilder pattern poetry doesn't even wind up in the actual deployed image. Separately, there's a bigger question of ergonomics, typing an extra |
This isn't related to the main post that closely, but a well-implemented solution to #537 with a But yeah, it probably won't help with the docker cache, since a bundle wouldn't make difference between dependencies (which are mostly static between builds) and the package that is under development and changes in every build... |
@a-recknagel I actually currently use poetry in all my containers used to deploy my poetry projects (despite also using requirements.txt) -- like @iwoloschin I've run into subtle issues when not using poetry directly; in particular, I previously had issues with the exported requirements.txt generating an invalid environment (I think there was an issue where the platform flags were generated wrong and I was missing one required dependency). Also, in some of my projects, I use poetry to perform C-extension compilation; I develop on macos so I also use I just use the requirements.txt to facilitate build caching, then add the pyproject.toml and poetry.lock and run poetry install (which runs much faster with the dependencies already pip installed, and the pip install step can generally stay cached for a while). From more of an abstract perspective, since I use poetry to set up my development environment, I just use poetry to set up the deployment environment to ensure it is as similar as possible (with minimal effort). If I could spare the time/energy to optimize container size or maximally lock down the image, I would certainly try to remove poetry from the final image, but this approach has been most efficient for me in development so far. |
@iwoloschin On the topic of export ergonomics, I have the following commands in a
(I use (Also, I think the without-hashes is safe since I follow up with a In the dockerfile, it looks like: RUN pip install "poetry==${POETRY_VERSION}"
COPY ./app/requirements/requirements-poetry.txt /app/requirements/
RUN pip install -r /app/requirements/requirements-poetry.txt
COPY ./app/requirements /app/requirements
RUN pip install \
--find-links=/app/requirements/wheels \
-r /app/requirements/requirements.txt
COPY ./app /app
RUN poetry install --no-dev Since the export happens automatically whenever I lock the dependencies, I never really have issues with forgetting a step prior to building the container. |
@dmontagu I tried using both pip and poetry in my CI before, and I had a bad time due to poetry implicitly creating virtual envs on a fresh I can see why having an "as similar as possible to dev" approach to build and deployment environments is a good idea, so I'd like to improve there. |
@a-recknagel When I have run into this problem in the past, I've worked around it by just manually pip-reinstalling the uninstalled packages after the Another approach I have used is to add a placeholder [tool.poetry]
name = "app"
version = "0.1.0"
description = "Placeholder"
authors = ["Placeholder"] Then run After reminding myself of all of these tricks necessary to get this to work nicely, I'm definitely in favor of a |
Didn't know that, nifty. I'm using preview anyways for The |
@a-recknagel Any suggestions for how to make use of the installer script in a container? Would you just call the Obviously there are many ways it could be done, but I'd be interested to hear if anyone had thought through the "best" way to accomplish this. |
@dmontagu I just do this: # Install Poetry & ensure it is in $PATH
RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | POETRY_PREVIEW=1 python
ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}" As you can see, I'm also using a virtual environment, because then later on I just copy the entire virtual environment out of the builder into the final container, leaving Poetry (and any other build dependencies!) behind. This is an artifact of me using an Alpine base, which makes wheels hard to use, so I need to install gcc & friends, but obviously do not want to keep those all around in the final image! Generally the installer script portion is cached unless I do a |
@dmontagu I was going to comment on the downside of the script installer, namely having Now that I read it, @iwoloschin's approach sounds best. After installing poetry, do you run the dependency install with |
Sure, here's a minimal version of my dockerfile: FROM python:3-alpine as python_builder
RUN apk update && \
apk add \
build-base \
curl \
git \
libffi-dev \
openssh-client \
postgresql-dev
# Install Poetry & ensure it is in $PATH
RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | POETRY_PREVIEW=1 python
ENV PATH "/root/.poetry/bin:/opt/venv/bin:${PATH}"
# Install any dependencies, this step will be cached so long as pyproject.toml is unchanged
# This could (should?) probably be changed to do a "pip install -r requirements.txt" in order
# to remain more cacheable, but it really depends on how long your dependencies take to install
COPY poetry.lock pyproject.toml /opt/project
RUN python -m venv /opt/venv && \
source /opt/venv/bin/activate && \
pip install -U pip && \
cd /opt/project && \
poetry install --no-dev --no-interaction
# Install the project itself (this is almost never cached)
COPY . /opt/project
RUN source /opt/venv/bin/activate && \
cd /opt/project && \
poetry install --no-dev --no-interaction
# Below this line is now creating the deployed container
# Anything installed above but not explicitly copied below is *not* available in the final container!
FROM python:3-alpine
# Any general deployed container setup that you want cached should go here
# Copy Project Virtual Environment from python_builder
COPY --from=python_builder /opt /opt
# Add the VirtualEnv to the beginning of $PATH
ENV PATH="/opt/venv/bin:$PATH"
CMD ... This allows my container to be moderately cacheable, so long as Of course, it goes without saying that the primary benefit here is that you can be "lazy" and use |
Issue #1333 was closed because I was told this issue was "very similar". If "poetry install" command documentation states the command is supposed to read the pyproject.toml file from the current project, resolves the dependencies, and installs the dependencies. Poetry should NOT be installing the current project as a dependency. The project is not a dependency to itself. If the current package is to be installed then that's a deployment, not dependency install. poetry install should only read the pyproject.toml and install dependencies. If a poetry.lock file exists, it could obey the locked dependencies. If the poetry.lock file does or does not exist, running poetry install should only install dependencies. That's why #1333 is really a bug and only partially related and should not have been closed. |
@jackemuk Of course a project depends on itself during development, poetry behaving like that isn't a bug. How are you going to test your library if it's not installed? |
@a-recknagel I don't yet know enough about this topic to have a position but generally I'd test my library by running the test suite or installing it as a dependency of a different application. |
@brycedrennan and the test suite should run against an installed version of your library, and not a bunch of source files in your working directory that may or may not behave similarly. Having a dev-install of the library you are working on is essential in order to have behavior that is as close to deployment as possible. Other reasons were also discussed in one of the earliest tickets in this project where people asked for a self install of the package in development - a request that is pretty much the polar opposite of #1333. |
For many basic python libraries "installation" is just putting source files on the python path. I also use I agree that this poetry behavior is not a bug since it was quite intentionally made to behave this way. I do find it to be unexpected default behavior though. |
@a-recknagel That's why there is a build command, to build and install the package/application. The application or library should not be installed to the development environment if the wheel or dist package has not been created. When another developer checks out the repo and installs the dependencies, the application that's being developed should not be installed automatically, It's not dependent on itself because the source is in the development environment. This creates a conflict between the source and the package installed in site-packages. We test our application by running either running it or running the test suite in the development environment or and build a wheel to deploy to a separate testing environment. The default should not install the package/application that is inside the pyproject.toml by default. If the package/application your working on is a dependency, then it should be listed in the dependency section in the pyproject.toml file. Just because it was an early request, doesn't make it right being the default behavior. Make it an option to install command as well as making it a user defined option that can make the install command also self-installs the package by default. That solves both issues. But, again, self install should not be the default when installing the dependencies listed in the pyproject.toml file. @brycedrennan a bug is unexpected behavior. The documentation doesn't say that it self installs the current environment and therefore I consider a bug. Either way, it shouldn't be the default behavior but having the ability to configure the default behavior of the install command would solve the issue. Making the default behavior be the equivalent to "pip install -e ." is contrary to working in a development environment, especially when there is no option to turn this behavior off. |
I don't want to hijack this comment section any longer to discuss a different issue, so this will by my last post regarding it. We're probably just going to have to disagree.
A dev install doesn't need to build a wheel/dist/sdist/whatever, it just needs an
No, it doesn't. You shouldn't have been consulting your source files in the first place when testing the behavior of a library, so there is no conflict. It will work most of the cases because python does nice things like adding the current directory to the
In that case use pip to install it, not poetry. poetry is a package manager targeting development, not build/test/deploy environments. It will always cater to dev environment first and foremost.
This just feels like a philosophical issue. Yeah, maybe it should be. But it wasn't, maybe because it was considered redundant to say that a library needs its own packaged code/config files/binaries in order to be valid, who knows.
Fair enough. But it means that you need a lot of public support to change it.
I'd agree here, but that point alone is hardly worth this much noise. |
@a-recknagel
Therefore it should not build and install the current development package as the default behavior.
Because my issue was closed and redirected the discussion under this issue. So unless #1333 is opened again for discussion by @brycedrennan, this is unfortunately where it landed. |
I forgot docker multi stage build. I'm now doing the same things like eliasmistler, using a python script to read the lock file and export requirements file with dev dependencies (in ci) or without (in production), with out installing poetry in my docker image but only |
If you're just trying to get your CI environment to build fast, (CircleCI, GitHub Actions, Travis etc.) you can take advantage of file system caching the I wrote a tutorial on how to do this if you're interested. |
This issue is related to #1899. I posted my current solution for caching there, and I'll post it here too, if it might help anyone: # Only copying these files here in order to take advantage of Docker cache. We only want the
# next stage (poetry install) to run if these files change, but not the rest of the app.
COPY pyproject.toml poetry.lock ./
# Currently poetry install is significantly slower than pip install, so we're creating a
# requirements.txt output and running pip install with it.
# Follow this issue: https://github.com/python-poetry/poetry/issues/338
# Setting --without-hashes because of this issue: https://github.com/pypa/pip/issues/4995
RUN poetry config virtualenvs.create false \
&& poetry export --without-hashes -f requirements.txt --dev \
| poetry run pip install -r /dev/stdin \
&& poetry debug
COPY . ./
# Because initially we only copy the lock and pyproject file, we can only install the dependencies
# in the RUN above, as the `packages` portion of the pyproject.toml file is not
# available at this point. Now, after the whole package has been copied in, we run `poetry install`
# again to only install packages, scripts, etc. (and thus it should be very quick).
# See this issue for more context: https://github.com/python-poetry/poetry/issues/1899
RUN poetry install --no-interaction
# We're setting the entrypoint to `poetry run` because poetry installed entry points aren't
# available in the PATH by default, but it is available for `poetry run`
ENTRYPOINT ["poetry", "run"] |
I agree with most of what's been said here regarding build cache breakage. Fundamentally, it doesn't seem like I wish I could do:
|
The
i.e. it suggests that only if poetry.lock is not found, will it go looking for pyproject.toml. So that would definitely be a huge step forward. |
@TBBle yes, that's always been a bit confusing! I've never seen an explanation as to why both files are necessary; without that, any arguments for needing both because poetry is a "Dev first" tool seem a bit insubstantial. If there's a reason for needing both, then that sort of prioritisation call is necessary. If not, then why not just solely rely on poetry.lock if it's present? |
I was looking for something else when I saw this, and wanted to offer my work around for the caching, as I think it results in a smaller docker image, and might be faster for rebuilds. I didn't think any of my docker images actually needed poetry installed, just the requirements export from it (and maybe the built wheel), so I did all of my poetry-dependent steps in a "builder" stage FROM python:3 as builder
SHELL ["/bin/bash", "-xeuo", "pipefail", "-c"]
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - \
&& ln -s "$HOME/.poetry/bin/poetry" /usr/local/bin/poetry
WORKDIR /app
COPY poetry.lock pyproject.toml ./
RUN mkdir -p /usr/local/build \
&& poetry export -o /usr/local/build/requirements.txt --without-hashes # --dev
# If you don't need the package itself installed, skip this step
COPY . /app
RUN rm -rf dist \
&& poetry build -f wheel \
&& mv dist/*.whl /usr/local/build \
&& echo "package-name @ file://$(ls /usr/local/build/*.whl)" >/usr/local/build/package.txt
FROM python:3
COPY --from=builder /usr/local/build/requirements.txt /usr/local/build/
RUN pip3 install -r /usr/local/build/requirements.txt \
&& rm -rf ~/.cache/pip
# or alternatively copy your working directory here
COPY --from=builder /usr/local/build/ /usr/local/build/
RUN pip3 install -r /usr/local/build/package.txt \
&& rm -rf ~/.cache/pip
CMD [ "python3", "-m", "package_name.entrypoint" ]
# CMD [ "python", "run.py"] I hope this helps someone. I re-use this pattern in several projects at work, rebuilds take seconds (after making changes), but I also don't need poetry in my final docker image. |
@akpircher Unfortunately this fails if you have path dependencies:
I am using a custom script to read the (see #936 (comment)) |
@remram44 I'm actually pretty familiar with that, back when poetry version 1.0.10, the local dependencies were fine, because poetry used a relative path in the export (I opened up #3189 because the upgrade to poetry 1.1.2 broke my Dockerfile. For a hot minute, I had been purposefully downgrading to 1.0.10). My solution to it is kinda dumb, but it takes advantage the multi-staged dockerfiles. It basically boils down to consistent path names. WORKDIR /usr/local/build/
COPY pyproject.toml poetry.lock ./
COPY dependencies dependencies/
RUN poetry export -f requirements.txt -o requirements.txt --without-hashes
# It's actually several copy commands of individual folders, so I don't copy the wheels over, but it's the gist
WORKDIR /usr/local/application-name
COPY . /usr/local/application-name
# I don't do this for this particular project, but I would if I actually needed to build it as a python wheel
RUN mkdir -p /usr/local/build-package \
&& poetry build -f wheel \
&& mv dist/*.whl /usr/local/build-package \
&& echo "package-name @ file://$(ls /usr/local/build-package/*.whl)" >/usr/local/build-package/package.txt
FROM python:3
# it HAS to be the same path.
COPY --from=builder /usr/local/build/ /usr/local/build/
RUN pip3 install --no-cache-dir -r /usr/local/build/requirements.txt \
&& rm -rf /usr/local/build/ ~/.cache/pip
# if needed, again the path MUST be the same
COPY --from=builder /usr/local/build-package/ /usr/local/build-package/
RUN pip3 install --no-cache-dir -r /usr/local/build-package/package.txt \
&& rm -rf /usr/local/build-package/ ~/.cache/pip
# Keep the non-"build" files somewhere else
COPY --from=builder /usr/local/application-name/ /user/local/application-name/ The copy of individual folders is a bit tedious, you could arguably copy the entire directory and then remove the third-party/dependency folder manually, but the idea behind taking care of the copies in the builder is (ultimately) to reduce how many layers I'm pushing to a private repository. |
So you copy the files into that first stage so I suppose that works but I'd much rather someone fixed the |
I only install once, in the final stage. I also only export once in the builder stage. You don't need to install in order to export. In the builder stage, I:
In the final stage, I:
The general workflow isn't too different from what @mcouthon offered. The primary difference is that I use multi-staged dockerfiles so that I never need to run It seems like people in this thread were desiring having the actual package installed, so "build wheel" step satisfies that, but is completely optional. The use case I have for it (in some scenarios) is that the built wheel contains a version from setuptools-scm which is displayed as the version when it's run. Otherwise, I don't really see the point of it. |
@remram44 I've created a dummy repo showing a concrete example of this. https://github.com/akpircher/multistage-dockerfile-demo. It would be nice for the export command to be fixed/be able to handle relative paths, but if you need something now, this works, caches the builds, and doesn't require running |
As posted in #3374 and similar to @akpircher above. Multi stage allows me to install just what I need for the application by generating the .whl and passing it to the final image https://github.com/compose-x/ecs_composex/blob/main/cli.Dockerfile |
After seeing all these comments I did my research and I think I finally arrived to a solution to this caching problem. What I understand, the problem is when you change something in your I also tried to merge all the opinionated practices here and set up a template here: https://github.com/scratchmex/poetry-docker-template. If you have opinions let me know. |
poetry install
is not Dockerfile cache compatiblepoetry install --no-root
with only poetry.lock
Renamed to better capture what makes this different from #4036 + best practices (like use of cache mounts). |
@mcouthon If
|
@varun-seth that's true, but I'm not sure what to do about it. Do you have any ideas? |
@mcouthon This script derives a minimalistic COPY poetry_to_pyproject.py poetry.lock ./
RUN python poetry_to_pyproject.py
RUN poetry install |
@varun-seth That's great! I have left a comment on your gist suggesting turning this into a Poetry plugin, to avoid the need to bundle the script as part of the Dockerfile. |
It takes me much time to figure it out until I find this thread. Here is the workaround I'm using: FROM busybox:1.35.0 as lockfile
WORKDIR /app
COPY pyproject.toml ./
# replace first matched version in pyproject.toml to 0.0.0
RUN awk '/version = "[^"]*"/ && !done {sub(/version = "[^"]*"/, "version = \"0.0.0\""); done=1} 1' pyproject.toml > tmpfile && mv tmpfile pyproject.toml WORKDIR /app
COPY --from=lockfile /app/pyproject.toml ./
COPY poetry.lock ./
RUN touch README.md
RUN poetry install -vv --without dev
COPY . . |
For anyone still looking for this functionality you can use https://pdm-project.org/latest/. It has command |
No activity for the last year (and no solution - I don't count creative workarounds - for more than 5...), so bumping - the issue is still fully valid. Other tools like |
Poetry re-resolves at install time with the lock file as only source. In other words, it needs the dependency specification from pyproject.toml. #9427 might be a game changer. In other words, building on #9427, it might be easier to implement this feature request. |
Feature Request
When doing
poetry install
, bothpyproject.toml
andpoetry.lock
are required. This makes sense as the package itself is installed along with its dependencies.However, this breaks caching in Docker. Let's say I have these lines in docker:
(Note how I also need the
README.md
as that is referenced in thepyproject.toml
)The main problem here is: On every build, I bump the version using
poetry version ...
. This changes thepyproject.toml
file, therefore breaks the docker caching mechanism. In comparison, with an additionalrequirements.txt
, I can do something like:In this case, all the dependencies (which quite often do not change between builds) can be cached. This speeds up my docker build by 80-90%, but it's obviously ugly to have to rely on
requirements.txt
FYI: I have a workaround for
requirements.txt
, where I simply generate it frompoetry.lock
:Alternatively, I could do some trickery around when to (re-)set the version, but this is getting uglier and uglier [plus I need requirements.txt anyway due to an other issue with poetry that I find really hard to pin down - more on that soon]
I would suggest adding the flag
poetry install --dependencies-only
which only requirespoetry.lock
, notpyproject.toml
The text was updated successfully, but these errors were encountered: