Project management with uv
In this guide, you'll learn how to use uv to manage your Apify Actor projects. From creating a project and running it locally to building and deploying it on the Apify platform.
Introduction
uv is a modern project and package manager for Python. It replaces the combination of pip, virtualenv, and similar tools with a single binary that manages your project's Python version, virtual environment, and dependencies. It records the project metadata in the standard pyproject.toml file and the exact resolved versions of all dependencies in a uv.lock lockfile.
The Python Actor templates declare their dependencies in a requirements.txt file, which is the default approach for Actors. Using uv instead brings a few advantages:
- The lockfile guarantees that the dependencies installed in the Actor's Docker image are exactly the ones that you locally developed and tested against.
- Dependency installation during the Docker build is significantly faster than with pip, especially with a warm cache.
- During local development, a single tool manages your Python version, virtual environment, and dependencies. As a result, the project behaves the same on every developer's machine.
The Apify Actor templates currently support only pip with requirements.txt. Adding uv-based templates is planned. For updates, follow apify/actor-templates#350.
Before you start
To follow along, install uv and the Apify CLI first.
Create a new project
To create a new uv project and add the Apify SDK to its dependencies, use:
uv init my-actor --bare
cd my-actor
uv python pin 3.14
uv add apify
Where:
uv initwith the--bareoption creates thepyproject.tomlproject manifest.uv python pinwrites the project's Python version to the.python-versionfile. uv automatically downloads that Python version if it's not installed on your machine.uv addrecords the dependency inpyproject.toml, resolves the exact versions of the whole dependency tree intouv.lock, and installs everything into the project's virtual environment in.venv.
The uv add command constrains the dependency to the latest version it resolved. You can edit the constraint as you see fit. The example Actor in this guide allows any version of the SDK within the current major one:
[project]
name = "my-actor"
version = "0.1.0"
description = "An Apify Actor with dependencies managed by uv."
requires-python = ">=3.14"
dependencies = [
"apify>=3.0.0,<4.0.0",
]
Note that the example has no [build-system] section. Without one, uv treats the project as a non-package ("virtual") project: it doesn't try to build and install the project itself, it only manages its dependencies. As a result, the Actor runs as a module straight from the source tree.
Add the Actor scaffolding
For the project to be runnable as an Actor, it needs two more pieces: the source code as a runnable Python package, and the .actor/ directory with the Actor configuration.
-
Create a
my_actorpackage with the Actor's source code:- my_actor/main.py
- my_actor/__main__.py
from apify import Actorasync def main() -> None:async with Actor:actor_input = await Actor.get_input() or {}Actor.log.info('Actor input: %s', actor_input)await Actor.set_value('OUTPUT', 'Hello from a uv-managed Actor!')import asynciofrom .main import mainif __name__ == '__main__':asyncio.run(main()) -
Add an empty
my_actor/__init__.pyfile, so that the directory is a regular Python package executable withpython -m my_actor. -
Add the Actor definition to
.actor/actor.json:.actor/actor.json{"$schema": "https://apify.com/schemas/v1/actor.ide.json","actorSpecification": 1,"name": "my-actor","title": "My uv Actor","description": "An Apify Actor with dependencies managed by uv.","version": "0.1","buildTag": "latest","dockerfile": "../Dockerfile"}
The dockerfile field points to the project's Dockerfile, which doesn't exist yet. You'll create it in the Use uv in the Dockerfile section.
The final project structure looks like this:
my-actor/
├── .actor/
│ └── actor.json
├── my_actor/
│ ├── __init__.py
│ ├── __main__.py
│ └── main.py
├── .python-version
├── Dockerfile
├── pyproject.toml
└── uv.lock
Make sure to commit uv.lock and .python-version to version control, so that every developer's machine works with identical dependencies and the same Python version. The Actor's Docker build gets its Python interpreter from the base image instead, so keep the base image tag (apify/actor-python:3.14) in sync with .python-version.
Run the Actor locally
If you've just cloned the project or skipped the uv add command, install the dependencies first:
uv sync
The uv sync command creates the .venv virtual environment (if it doesn't exist yet) and installs the locked dependencies into it. Then, run the Actor with the Apify CLI:
apify run
The apify run command automatically detects the virtual environment in .venv and uses it to run the Actor as a module (python -m my_actor), with the environment set up to emulate the Apify platform locally. For example, the Actor input is read from storage/key_value_stores/default/INPUT.json.
Use uv in the Dockerfile
On the Apify platform, the Actor runs as a Docker container built from the Dockerfile referenced in .actor/actor.json. The following Dockerfile installs the locked dependencies with uv on top of the Apify Python base image:
# syntax=docker/dockerfile:1
# First, specify the base Docker image.
# You can see the Docker images from Apify at https://hub.docker.com/r/apify/.
# You can also use any other image from Docker Hub.
FROM apify/actor-python:3.14
# Add the uv binary from its official distroless image (pinned to the 0.11.x line).
COPY /uv /uvx /bin/
# Configure uv for container builds:
# - compile installed packages to bytecode, so the Actor starts faster,
# - copy packages instead of hardlinking, which avoids warnings with the cache mount,
# - never download a managed Python, always reuse the base image's interpreter,
# - put the project virtual environment first on PATH, so `python` resolves to it.
ENV UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy \
UV_PYTHON_DOWNLOADS=0 \
PATH="/usr/src/app/.venv/bin:$PATH"
# Install dependencies into the project virtual environment (.venv) as a separate
# layer. The cache mount speeds up repeated builds, and the bind mounts make the
# project metadata available without copying it into the image. This layer is
# rebuilt only when uv.lock or pyproject.toml change - not on source code edits.
RUN
\
uv sync --locked --no-dev
# Next, copy the remaining files and directories with the source code.
# Since we do this after installing the dependencies, quick rebuilds will be
# really fast for most source file changes.
COPY . ./
# Use compileall to ensure the runnability of the Actor Python code.
RUN python -m compileall -q my_actor/
# Specify how to launch the source code of your Actor.
CMD ["python", "-m", "my_actor"]
Note that:
- The uv binary is copied from its official Docker image, pinned to a minor version line, so builds are reproducible and there is no need to install uv with pip.
uv sync --locked --no-devinstalls the dependencies exactly as recorded inuv.lockand skips development dependencies. If the lockfile is missing or out of sync withpyproject.toml, the build fails instead of silently resolving different versions.- The dependencies are installed in a separate layer before the source code is copied, so editing your code doesn't invalidate the dependency layer, and rebuilds are fast.
- Putting
.venv/binfirst onPATHmakespythonresolve to the project's virtual environment, both during the build and when the Actor runs.
Also create a .dockerignore file and exclude at least .venv, .git, and storage from the Docker build context. The local virtual environment must never be copied into the image, since it's recreated by uv sync during the build.
Deploy to the Apify platform
Once the Actor works locally, log in and push it to the Apify platform:
apify login
apify push
The apify push command uploads the project to the platform and builds the Docker image from the Dockerfile above. Thanks to the committed lockfile, the platform build installs exactly the dependency versions you ran locally.
Manage dependencies
Day-to-day dependency management goes through uv as well:
# Add a dependency (records it in pyproject.toml and updates uv.lock).
uv add httpx
# Add a development-only dependency (skipped in the Docker build by --no-dev).
uv add --dev ruff
# Remove a dependency.
uv remove httpx
# Upgrade all dependencies to the latest versions allowed by pyproject.toml.
uv lock --upgrade
uv sync
Whenever the dependencies change, commit the updated uv.lock together with pyproject.toml.
Conclusion
In this guide, you learned how to use uv to manage Apify Actor projects. You can now create a uv project with the Apify SDK, run it locally with the Apify CLI, install the locked dependencies with uv in the Actor's Docker image, and deploy the whole project to the Apify platform with reproducible builds. If you have questions or need assistance, feel free to reach out on our GitHub or join our Discord community. Happy coding!