1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412# [Using uv in GitHub Actions](#using-uv-in-github-actions)
## [Installation](#installation)
For use with GitHub Actions, we recommend the official [`astral-sh/setup-uv`](https://github.com/astral-sh/setup-uv) action, which installs uv, adds it to PATH, (optionally) persists the cache, and more, with support for all uv-supported platforms.
To install the latest version of uv:
example.yml
```
name: Example
jobs:
uv-example:
name: python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
```
It is considered best practice to pin to a specific uv version, e.g., with:
example.yml
```
name: Example
jobs:
uv-example:
name: python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
# Install a specific version of uv.
version: "0.9.26"
```
## [Setting up Python](#setting-up-python)
Python can be installed with the `python install` command:
example.yml
```
name: Example
jobs:
uv-example:
name: python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Set up Python
run: uv python install
```
This will respect the Python version pinned in the project.
Alternatively, the official GitHub `setup-python` action can be used. This can be faster, because GitHub caches the Python versions alongside the runner.
Set the [`python-version-file`](https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md#using-the-python-version-file-input) option to use the pinned version for the project:
example.yml
```
name: Example
jobs:
uv-example:
name: python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install uv
uses: astral-sh/setup-uv@v7
```
Or, specify the `pyproject.toml` file to ignore the pin and use the latest version compatible with the project's `requires-python` constraint:
example.yml
```
name: Example
jobs:
uv-example:
name: python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"
- name: Install uv
uses: astral-sh/setup-uv@v7
```
## [Multiple Python versions](#multiple-python-versions)
When using a matrix to test multiple Python versions, set the Python version using `astral-sh/setup-uv`, which will override the Python version specification in the `pyproject.toml` or `.python-version` files:
example.yml
```
jobs:
build:
name: continuous-integration
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@v6
- name: Install uv and set the Python version
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python-version }}
```
If not using the `setup-uv` action, you can set the `UV_PYTHON` environment variable:
example.yml
```
jobs:
build:
name: continuous-integration
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v6
```
## [Syncing and running](#syncing-and-running)
Once uv and Python are installed, the project can be installed with `uv sync` and commands can be run in the environment with `uv run`:
example.yml
```
name: Example
jobs:
uv-example:
name: python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run tests
# For example, using `pytest`
run: uv run pytest tests
```
Tip
The [`UV_PROJECT_ENVIRONMENT` setting](../../../concepts/projects/config/#project-environment-path) can be used to install to the system Python environment instead of creating a virtual environment.
## [Caching](#caching)
It may improve CI times to store uv's cache across workflow runs.
The [`astral-sh/setup-uv`](https://github.com/astral-sh/setup-uv) has built-in support for persisting the cache:
example.yml
```
- name: Enable caching
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
```
Alternatively, you can manage the cache manually with the `actions/cache` action:
example.yml
```
jobs:
install_job:
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: /tmp/.uv-cache
steps:
# ... setup up Python and uv ...
- name: Restore uv cache
uses: actions/cache@v5
with:
path: /tmp/.uv-cache
key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
restore-keys: |
uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
uv-${{ runner.os }}
# ... install packages, run tests, etc ...
- name: Minimize uv cache
run: uv cache prune --ci
```
The `uv cache prune --ci` command is used to reduce the size of the cache and is optimized for CI. Its effect on performance is dependent on the packages being installed.
Tip
If using `uv pip`, use `requirements.txt` instead of `uv.lock` in the cache key.
Note
When using non-ephemeral, self-hosted runners the default cache directory can grow unbounded. In this case, it may not be optimal to share the cache between jobs. Instead, move the cache inside the GitHub Workspace and remove it once the job finishes using a [Post Job Hook](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/running-scripts-before-or-after-a-job).
```
install_job:
env:
# Configure a relative location for the uv cache
UV_CACHE_DIR: ${{ github.workspace }}/.cache/uv
```
Using a post job hook requires setting the `ACTIONS_RUNNER_HOOK_JOB_STARTED` environment variable on the self-hosted runner to the path of a cleanup script such as the one shown below.
clean-uv-cache.sh
```
#!/usr/bin/env sh
uv cache clean
```
## [Using `uv pip`](#using-uv-pip)
If using the `uv pip` interface instead of the uv project interface, uv requires a virtual environment by default. To allow installing packages into the system environment, use the `--system` flag on all `uv` invocations or set the `UV_SYSTEM_PYTHON` variable.
The `UV_SYSTEM_PYTHON` variable can be defined in at different scopes.
Opt-in for the entire workflow by defining it at the top level:
example.yml
```
env:
UV_SYSTEM_PYTHON: 1
jobs: ...
```
Or, opt-in for a specific job in the workflow:
example.yml
```
jobs:
install_job:
env:
UV_SYSTEM_PYTHON: 1
...
```
Or, opt-in for a specific step in a job:
example.yml
```
steps:
- name: Install requirements
run: uv pip install -r requirements.txt
env:
UV_SYSTEM_PYTHON: 1
```
To opt-out again, the `--no-system` flag can be used in any uv invocation.
## [Private repos](#private-repos)
If your project has [dependencies](../../../concepts/projects/dependencies/#git) on private GitHub repositories, you will need to configure a [personal access token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) to allow uv to fetch them.
After creating a PAT that has read access to the private repositories, add it as a [repository secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
Then, you can use the [`gh`](https://cli.github.com/) CLI (which is installed in GitHub Actions runners by default) to configure a [credential helper for Git](../../../concepts/authentication/git/#git-credential-helpers) to use the PAT for queries to repositories hosted on `github.com`.
For example, if you called your repository secret `MY_PAT`:
example.yml
```
steps:
- name: Register the personal access token
run: echo "${{ secrets.MY_PAT }}" | gh auth login --with-token
- name: Configure the Git credential helper
run: gh auth setup-git
```
## [Publishing to PyPI](#publishing-to-pypi)
uv can be used to build and publish your package to PyPI from GitHub Actions. We provide a standalone example alongside this guide in [astral-sh/trusted-publishing-examples](https://github.com/astral-sh/trusted-publishing-examples). The workflow uses [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no credentials need to be configured.
In the example workflow, we use a script to test that the source distribution and the wheel are both functional and we didn't miss any files. This step is recommended, but optional.
First, add a release workflow to your project:
.github/workflows/publish.yml
```
name: "Publish"
on:
push:
tags:
# Publish on any tag starting with a `v`, e.g., v0.1.0
- v*
jobs:
run:
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install Python 3.13
run: uv python install 3.13
- name: Build
run: uv build
# Check that basic features work and we didn't miss to include crucial files
- name: Smoke test (wheel)
run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
- name: Smoke test (source distribution)
run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py
- name: Publish
run: uv publish
```
Then, create the environment defined in the workflow in the GitHub repository under "Settings" -> "Environments".
Add a [trusted publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) to your PyPI project in the project settings under "Publishing". Ensure that all fields match with your GitHub configuration.
After saving:
Finally, tag a release and push it. Make sure it starts with `v` to match the pattern in the workflow.
```
$ git tag -a v0.1.0 -m v0.1.0
$ git push --tags
```