diff --git a/django/getting-started/index.html.md b/django/getting-started/index.html.md index fb0ef28332..3454346a65 100644 --- a/django/getting-started/index.html.md +++ b/django/getting-started/index.html.md @@ -34,7 +34,11 @@ mkdir hello-django && cd hello-django ### Virtual Environment -For this guide, we use [venv](https://docs.python.org/3/library/venv.html#module-venv) but any of the other popular choices such as [Poetry](https://python-poetry.org/), [Pipenv](https://github.com/pypa/pipenv), or [pyenv](https://github.com/pyenv/pyenv) work too. +For this guide, we use [venv](https://docs.python.org/3/library/venv.html#module-venv) but any of the other popular choices such as +[uv](https://docs.astral.sh/uv/), +[Poetry](https://python-poetry.org/), +[Pipenv](https://github.com/pypa/pipenv), or +[pyenv](https://github.com/pyenv/pyenv) work too. ```shell # Unix/macOS diff --git a/python/do-more/add-object-storage.html.markerb b/python/do-more/add-object-storage.html.markerb index 7aaeab69d2..09c4e6a906 100644 --- a/python/do-more/add-object-storage.html.markerb +++ b/python/do-more/add-object-storage.html.markerb @@ -20,7 +20,7 @@ fly secrets list The de-facto library for interacting with s3 storage is [boto3](https://pypi.org/project/boto3/+external), let's add it to the project: ```cmd -poetry add boto3 +uv add boto3 ``` Now we can initialize the client: diff --git a/python/do-more/add-ollama.html.markerb b/python/do-more/add-ollama.html.markerb index 2058f5ec1e..4320f0c1e1 100644 --- a/python/do-more/add-ollama.html.markerb +++ b/python/do-more/add-ollama.html.markerb @@ -16,7 +16,7 @@ fly secrets set OLLAMA_HOST=http://.flycast To interact with our new AI friend, we will have to install the `ollama` package: ```cmd -poetry add ollama +uv add ollama ``` Now we can initialize the client: diff --git a/python/do-more/add-postgres.html.markerb b/python/do-more/add-postgres.html.markerb index e631ed3392..f115558d46 100644 --- a/python/do-more/add-postgres.html.markerb +++ b/python/do-more/add-postgres.html.markerb @@ -42,7 +42,7 @@ At this point you can use a database driver to interact with the database. You h Let's create a function to get some metadata about the database using `asyncpg`: ```cmd -poetry add asyncpg +uv add asyncpg ``` ```python diff --git a/python/frameworks/fastapi.html.markerb b/python/frameworks/fastapi.html.markerb index 42ed61848b..b5ea8e2d2b 100644 --- a/python/frameworks/fastapi.html.markerb +++ b/python/frameworks/fastapi.html.markerb @@ -22,13 +22,13 @@ Deploying a FastAPI app on Fly.io is... well it's fast! You can be up and runnin ## _Deploy a FastAPI app from scratch_ -<%= partial "/docs/python/partials/poetry_new", locals: { runtime: "FastAPI" } %> +<%= partial "/docs/python/partials/uv_init", locals: { runtime: "FastAPI" } %> Then we have to add the FastAPI dependency: ```cmd -poetry add "fastapi[standard]" +uv add "fastapi[standard]" ``` Now, let's create a simple FastAPI app in `main.py`: diff --git a/python/frameworks/flask.html.markerb b/python/frameworks/flask.html.markerb index 51c5cd646a..68af93fa7b 100644 --- a/python/frameworks/flask.html.markerb +++ b/python/frameworks/flask.html.markerb @@ -18,11 +18,11 @@ Spinning up a flask app is fun! <%= partial "/docs/languages-and-frameworks/partials/flyctl" %> -<%= partial "/docs/python/partials/speedrun", locals: { runtime: "Flask", repo: "hello-flask-poetry" } %> +<%= partial "/docs/python/partials/speedrun", locals: { runtime: "Flask", repo: "hello-flask-uv" } %> ## _Deploy a Flask app from scratch_ -<%= partial "/docs/python/partials/poetry_new", locals: { runtime: "Flask" } %> +<%= partial "/docs/python/partials/uv_init", locals: { runtime: "Flask" } %> To run this app we will need 2 dependencies: @@ -30,7 +30,7 @@ To run this app we will need 2 dependencies: - gunicorn: the server ```cmd -poetry add flask gunicorn +uv add flask gunicorn ``` Now let's create a basic app in `app.py`: @@ -67,4 +67,4 @@ flask --app hello run If you open http://127.0.0.1:5000/ in your web browser, it should display `hello from fly.io`. -<%= partial "/docs/python/partials/deploy", locals: { runtime: "Flask", repo: "hello-flask-poetry" } %> +<%= partial "/docs/python/partials/deploy", locals: { runtime: "Flask", repo: "hello-flask-uv" } %> diff --git a/python/frameworks/streamlit.html.markerb b/python/frameworks/streamlit.html.markerb index 1d9715f099..542fd396ec 100644 --- a/python/frameworks/streamlit.html.markerb +++ b/python/frameworks/streamlit.html.markerb @@ -22,13 +22,13 @@ Spinning up a `streamlit` app takes no time at all! ## _Deploy a Streamlit from scratch_ -<%= partial "/docs/python/partials/poetry_new", locals: { runtime: "Streamlit" } %> +<%= partial "/docs/python/partials/uv_init", locals: { runtime: "Streamlit" } %> Then we have to add the streamlit dependency: ```cmd -poetry add streamlit +uv add streamlit ``` Now, let's create a simple streamlit app in `main.py`: @@ -56,4 +56,4 @@ headless = true This ensures that our app doesn't give a prompt when we ship it. -<%= partial "/docs/python/partials/deploy", locals: { runtime: "streamlit", repo: "hello-streamlit" } %> \ No newline at end of file +<%= partial "/docs/python/partials/deploy", locals: { runtime: "streamlit", repo: "hello-streamlit" } %> diff --git a/python/partials/_poetry_new.erb b/python/partials/_poetry_new.erb deleted file mode 100644 index 8e8a7cdb96..0000000000 --- a/python/partials/_poetry_new.erb +++ /dev/null @@ -1,9 +0,0 @@ -For managing our project, we use [Poetry](https://python-poetry.org/). -For more information on the initial setup with poetry, refer to [setting up a python environment](/docs/python/the-basics/initial-setup). -We can initialize a new project like so: - -```cmd -poetry new <%= runtime.downcase %>-app -cd <%= runtime.downcase %>-app -poetry shell -``` \ No newline at end of file diff --git a/python/partials/_uv_init.erb b/python/partials/_uv_init.erb new file mode 100644 index 0000000000..ddf31cf085 --- /dev/null +++ b/python/partials/_uv_init.erb @@ -0,0 +1,9 @@ +For managing our project, we use [uv](https://docs.astral.sh/uv/). +For more information on the initial setup with uv, refer to ["Setting up a Python Environment"](/docs/python/the-basics/initial-setup). +We can initialize a new project like so: + +```cmd +uv init <%= runtime.downcase %>-app +cd <%= runtime.downcase %>-app +uv run python +``` diff --git a/python/the-basics/initial-setup.html.md b/python/the-basics/initial-setup.html.md index 1236a65bdb..f89d65fe94 100644 --- a/python/the-basics/initial-setup.html.md +++ b/python/the-basics/initial-setup.html.md @@ -17,48 +17,35 @@ We recommend the latest [supported versions](https://devguide.python.org/version ## Dependency Management -For project and dependency management we use [Poetry](https://python-poetry.org/). Like most package managers, Poetry combines multiple tools in one. +For project and dependency management we use [uv](https://docs.astral.sh/uv/). Like most package managers, uv combines multiple tools in one. You have other options: - [venv](https://docs.python.org/3/library/venv.html) and [pip](https://pip.pypa.io/) +- [Poetry](https://python-poetry.org/) - [Pipenv](https://github.com/pypa/pipenv) - [pyenv](https://github.com/pyenv/pyenv) - [pip-tools](https://pypi.org/project/pip-tools/) -If you are just starting out, it is easiest to follow along using `poetry`. -After installing it, you will have to set 2 flags for virtual environment management. - -```cmd -poetry config virtualenvs.create true -poetry config virtualenvs.in-project true -``` +If you are just starting out, it is easiest to follow along using `uv`. This will make your development environment resemble what ends up happening inside the docker image. You can create a new project using this command: ```cmd -poetry new +uv init ``` Once inside the project, you can add packages with the add command: ```cmd -poetry add +uv add ``` This will automatically create a virtual environment for you. -To interact with your virtual environment, you can prefix your commands with `poetry run`: - -```cmd -poetry run python main.py -``` - -Alternatively, you can activate the environment: +To interact with your virtual environment, you can prefix your commands with `uv run`: ```cmd -poetry shell -python main.py +uv run python main.py ``` - diff --git a/python/the-basics/multi-stage-builds.html.md b/python/the-basics/multi-stage-builds.html.md index 27162ad4db..889b3eebbd 100644 --- a/python/the-basics/multi-stage-builds.html.md +++ b/python/the-basics/multi-stage-builds.html.md @@ -12,47 +12,54 @@ In short, we split the process of building and compiling dependencies and runnin - The resulting image is smaller in size - The 'attack surface' of your application is smaller -Let's make a multi-stage `Dockerfile` from scratch. Here's part 1: - -
-In this example we assume the use of `poetry`, however you can adapt the file to work with other dependency managers too. -
+Let's make a multi-stage `Dockerfile` using uv, based on the [uv-docker-example](https://github.com/astral-sh/uv-docker-example/raw/refs/heads/main/multistage.Dockerfile). Here's part 1: ```dockerfile -FROM python:3.11.9-bookworm AS builder - -ENV PYTHONUNBUFFERED=1 \ - PYTHONDONTWRITEBYTECODE=1 +# Builder stage +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder -RUN pip install poetry && poetry config virtualenvs.in-project true +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy WORKDIR /app -COPY pyproject.toml poetry.lock ./ +# Install dependencies +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project --no-dev -RUN poetry install +# Copy the application code +ADD . /app + +# Install the project +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev ``` -So what's going on here? First, we use a "fat" python 3.11.9 image and installing and building all dependencies. Defining it as `builder` gives us a way to interact with it later. What essentially happens here is exactly what happens when you install a project locally using poetry: a `.venv/` directory is created and in it are all your built dependencies and binaries. You can inspect your own `.venv/` folder to see what that looks like. This directory is the primary artifact that we want. +So what's going on here? First, we use a slim Python image that includes `uv`. We set some environment variables and install all dependencies using `uv sync`. This stage is defined as `builder`, which gives us a way to interact with it later. + +What essentially happens here is similar to what happens when you install a project locally using a package manager: dependencies are installed and the project is set up. The primary artifact we want is the installed project with all its dependencies. Part 2, the runtime, looks something like this: ```dockerfile -FROM python:3.11.9-slim-bookworm +# Runtime stage +FROM python:3.12-slim-bookworm -WORKDIR /app +# Copy the application from the builder +COPY --from=builder --chown=app:app /app /app -COPY --from=builder /app . -COPY [python-app]/ ./[python-app] +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" -CMD ["/app/.venv/bin/python", "[python-app]/app.py"] +# Run the application +CMD ["python", "/app/src/your_app_name/main.py"] ``` -Here we see very little actually going on; instead of the "fat" image, we now pick the slim variant. This one is about 5 times smaller in size, but is unable to compile many of the dependencies we would want compiled. We have already done that part though, so we can copy that `.venv/` folder over to this image without having to compile it again. +Here we see very little actually going on; we use a slim Python image that matches the builder's Python version. We've already done the compilation and installation in the builder stage, so we can copy the entire `/app` directory (which includes the virtual environment) from the builder stage to this runtime image. With this setup our image will be around 200MB most of the time (depending on what else you include). This setup is used for nearly all Python apps you deploy on Fly.io.
The image size is largely dependent on what files you add in the dockerfile; by default the entire working directory is copied in. If you do not want to add certain files, you can specify them in a `.dockerignore` file.
-