This is an attempt to make a nice multi-stage docker build that leverages poetry for building environment, has poetry available for a development stage, but keeps production stage to a small size by only copying the virtual environment that poetry created.
Poetry is useful to manage dependencies; however, Poetry itself is not necessary as part of a production image (only the environment that it created is needed in production).
Docker multi-stage builds allow for the usage of poetry in build & development stages while allowing for a production stage without poetry if the virtual environment is copied from the build stage.
Unfortunately poetry makes that complicated because it does not give complete control over the naming scheme of the virtual environment.
- Create a build stage (base build)
- Use python
venv
to create a virtual environment somewhere outside of anywhere mapped volumes might go. - Poetry installs runtime deps to that virtual environment
- Use python
- Create a separate development image from base build
- poetry installs development dependencies to virtual environment
- poetry is available for future needs
- Create a production image from a pred-prod stage
- In a pre-prod stage, create app skeleton and install the app as a package (symlink)
- Copy ONLY the virtual environment (containing only runtime dependencies) and app code to the production image
Poetry has a specific way of handling virtual environments that makes this approach seem slightly complicated.
- if a virtual environment is activated, poetry installs there
- otherwise, if
POETRY_VIRTUALENVS_IN_PROJECT=true
, poetry installs to a folder inside project root called.venv
- otherwise, poetry installs to directory at
POETRY_VIRTUALENVS_PATH
and creates a slug name for the folder (sort of like "app-root-dir-name-<some sLuGoFchArs>-py3.x"), which is difficult to reference later
The approach used in this Dockerfile
relies on #1 (setting VIRTUAL_ENV
so poetry installs there).
#2 is undesirable because we're often trying to mount the application code from local machine so it can't create the virtual environment without getting overshadowed by mount. Also don't want the virtual environment to be part of the build context.
#3 is difficult to copy to the production image from the builder stage due to the naming scheme that is not controllable.
docker compose build
docker compose up
docker build . -t app
docker run -it -p 0.0.0.0:8000:8000 app
Relied heavily on comments in this issue python-poetry/poetry#1879 and articles from https://pythonspeed.com