Use uv with JFrog CLI
Run uv commands with Artifactory integration for credential injection, optional build-info collection for dependencies and Python packages, and checksum enrichment when indexes point at Artifactory.
This topic covers the following tasks:
When to Use
Use jf uv for Python projects managed with uv—typically a pyproject.toml (and usually uv.lock) in the project root. Running through JFrog CLI forwards arguments to the uv binary, injects credentials from your JFrog CLI server configuration when appropriate, and can record resolved dependencies and built artifacts for build-info.
For requirements.txt workflows, use jf pip. For Pipfile projects, use jf pipenv. For Poetry, use jf poetry.
If you do not need build-info or JFrog-managed credentials, you can run uv directly and configure indexes in pyproject.toml or environment variables alone.
Prerequisites
- uv installed on the machine and on
PATH. Verify withuv --version. - JFrog CLI installed and authenticated — install or upgrade from the JFrog CLI quick start, then configure a server with
jf config addorjf c add. The CLI uses this configuration (default server, or the server named by--server-id) to supply credentials when native UV mechanisms do not already cover the index or publish URL host. To publish build-info withjf rt build-publish, the configured credentials must be valid for that server. - Access token caveat — if you use a non-JWT access token, ensure your JFrog CLI configuration includes a username where required by your Artifactory setup.
- Optional — set
JFROG_HOMEif you use a non-default directory for JFrog CLI configuration files. - Artifactory PyPI repositories — create local, remote, and virtual PyPI repositories as needed. Index URLs in
pyproject.tomlshould reference your virtual or remote repository (for examplehttps://<artifactory-host>/artifactory/api/pypi/<virtual-repo>/simple). - Project layout — build-info collection expects a valid uv project; operations that need
pyproject.tomlfail if it is missing.
Build: jf uv
jf uvjf uv runs uv with stdin, stdout, and stderr passed through. JFrog CLI removes its own options (--build-name, --build-number, --module, --project, --server-id) from the argument list wherever they appear, then passes the remaining tokens to uv in order.
When --build-name and --build-number are both set (or supplied via JFROG_CLI_BUILD_NAME and JFROG_CLI_BUILD_NUMBER, or resolved from the same mechanisms as other build tools—including optional project build configuration under .jfrog), the CLI collects build-info after a successful run. Dependency-oriented commands enrich checksums from Artifactory when the first [[tool.uv.index]] URL host matches the configured Artifactory host (or the host for --server-id).
To run uv with Artifactory integration:
Synopsis
jf uv [jfrog-options ...] <uv-subcommand> [uv-args ...]
JFrog options are: --build-name, --build-number, --module, --project, --server-id. They may appear before or after the uv subcommand (for example jf uv sync --build-name=a --build-number=1 or jf uv --build-name=a --build-number=1 sync).
Aliases: none.
Arguments
| Argument | Required | Description |
|---|---|---|
<uv-subcommand> | Yes | First token passed to uv after JFrog-specific options are removed (for example sync, install, lock, add, remove, run, build, publish). Remaining arguments and flags are forwarded to uv unchanged. |
Build Options
| Flag | Default | Description |
|---|---|---|
--build-name | (none) | Logical build name; requires --build-number when set |
--build-number | (none) | Build number or CI id; requires --build-name when set |
--module | (none) | Overrides the build-info module id |
--project | (none) | Associates the build-info with a JFrog Project |
--server-id | Default server | JFrog CLI server id used for credential injection and dependency checksum enrichment |
Configure indexes and publishing
There is no jf uv-config command. Configure uv the native way:
- Add
[[tool.uv.index]]entries inpyproject.tomlwith anameandurlpointing at Artifactory. - Optionally set
publish-urlunder[tool.uv]foruv publish. - Ensure
jf config(or--server-id) points at the same Artifactory host when you want the CLI to injectUV_INDEX_<NAME>_USERNAME/UV_INDEX_<NAME>_PASSWORDfor each named index whose URL host matches the server URL.
The environment suffix for an index name is the uppercased name with -, ., and spaces replaced by _ (for example pypi-virtual → PYPI_VIRTUAL, so variables are UV_INDEX_PYPI_VIRTUAL_USERNAME and UV_INDEX_PYPI_VIRTUAL_PASSWORD).
If you already set those variables, embed credentials in the index URL, or use .netrc for the index host, the CLI does not override them. When the CLI injects index credentials, it sets UV_KEYRING_PROVIDER=disabled.
All other flags pass through to uv — JFrog CLI only removes --build-name, --build-number, --module, --project, and --server-id. Every other token is forwarded to uv unchanged (for example --frozen, --no-dev, --index-strategy=first-index).
Help — jf uv --help and jf uv <subcommand> --help invoke uv directly.
Example pyproject.toml (index + publish URL)
pyproject.toml (index + publish URL)[[tool.uv.index]]
name = "pypi-virtual"
url = "https://<artifactory-host>/artifactory/api/pypi/<virtual-repo>/simple"
default = true
[tool.uv]
# JFrog CLI convention: jf uv reads this value and appends --publish-url to uv publish.
# This key is not recognized by uv itself and will not appear in uv's documentation.
publish-url = "https://<artifactory-host>/artifactory/api/pypi/<local-repo>"Build Examples
Basic sync
cd <project-root>
jf uv syncWhere:
<project-root>is the directory that containspyproject.toml
For example:
cd ~/projects/my-app
jf uv syncOn success, uv typically resolves dependencies, updates or creates .venv, and exits 0. Exact log lines depend on your project and UV version.
Sync with build-info
jf uv sync --build-name=<build-name> --build-number=<build-number>Where:
<build-name>is the logical name for the build<build-number>is the build id (for example a CI run number)
For example:
jf uv sync --build-name=my-app --build-number=42Explicit Artifactory server
jf uv sync --build-name=<build-name> --build-number=<build-number> --server-id=<server-id>Where:
<server-id>is the server alias fromjf config add
For example:
jf uv sync --build-name=my-app --build-number=42 --server-id=artifactory-prodLock or install with build-info
jf uv lock --build-name=<build-name> --build-number=<build-number>jf uv install --build-name=<build-name> --build-number=<build-number>For example:
jf uv lock --build-name=my-app --build-number=42Add, remove, or run
jf uv add <package> --build-name=<build-name> --build-number=<build-number>
jf uv remove <package> --build-name=<build-name> --build-number=<build-number>
jf uv run <command> --build-name=<build-name> --build-number=<build-number>Where:
<package>is a dependency name accepted byuv add/uv remove<command>is the program and arguments passed touv run
For example:
jf uv add httpx --build-name=my-app --build-number=42
jf uv run python -c "print('ok')" --build-name=my-app --build-number=42Build wheels and sdists
jf uv build --build-name=<build-name> --build-number=<build-number>For example:
jf uv build --build-name=my-app --build-number=42When build-info is enabled, jf uv build records only the module ID — no dependencies and no artifacts. Dependencies are cleared to prevent duplicates if jf uv sync (or another dependency command) already ran under the same build name and number. Artifact recording happens at jf uv publish.
Default module ID — When --module is omitted, the build-info module id is {project.name}:{project.version} from pyproject.toml (for example my-app:1.2.0).
Build-info by command (when --build-name and --build-number are both set):
| uv subcommand | Dependencies in build-info | Artifacts in build-info |
|---|---|---|
sync, install, lock, add, remove, run | Yes | No |
build | Cleared (module ID only) | No |
publish | Unchanged from prior steps | Yes (.whl, .tar.gz) |
Dependency records use id name:version, type whl or tar.gz, and no scopes. Published artifacts use type wheel or sdist.
Checksum enrichment uses the first [[tool.uv.index]] entry in pyproject.toml (or UV_DEFAULT_INDEX / UV_INDEX_URL when no index entry exists) to match the configured Artifactory host.
The intended flow is:
jf uv sync— resolves and records dependencies in build-infojf uv build— builds the distribution; records only the module IDjf uv publish— uploads artifacts and records them in build-info
Publish: jf uv publish
jf uv publishPublishing uses the same jf uv entrypoint with the publish subcommand.
Before you publish
- Run
jf uv buildsodist/contains.whland.tar.gz.jf uv publishfails ifdist/is missing or empty. - Set
publish-urlto a local PyPI repository (.../api/pypi/<local-repo>). Do not publish to a remote or virtual repository.
Credentials for publish follow this priority: UV_PUBLISH_TOKEN; or UV_PUBLISH_USERNAME / UV_PUBLISH_PASSWORD; or credentials embedded in the publish URL; or .netrc for the publish URL host; otherwise same-host index credentials from [[tool.uv.index]]; otherwise the JFrog CLI server user/password or token when the publish URL host matches the configured Artifactory host.
On publish, if --publish-url does not appear in the arguments forwarded to uv but publish-url is set under [tool.uv] in pyproject.toml, the CLI reads that value and appends --publish-url before running uv. Note that publish-url is a JFrog CLI convention and is not a key that uv itself recognizes.
To publish with optional build-info:
Synopsis
jf uv publish [jfrog-options ...] [uv-publish-args ...]
The same rule applies: JFrog options may appear anywhere in the command line before uv runs.
Aliases: none.
Arguments
| Argument | Required | Description |
|---|---|---|
publish | Yes | Literal first argument selecting uv publish |
| Remaining args | No | Any flags and values accepted by uv publish (for example --publish-url); forwarded unchanged |
Publish options (JFrog CLI)
The same JFrog flags apply as in Build Options.
Publish Examples
Publish after build
jf uv build
jf uv publish --build-name=<build-name> --build-number=<build-number>Where:
<build-name>and<build-number>identify the build-info to update
For example:
jf uv build
jf uv publish --build-name=my-app --build-number=42Publish URL on the command line
jf uv publish --publish-url=<publish-url> --build-name=<build-name> --build-number=<build-number>Where:
<publish-url>is the full Artifactory PyPI API URL for the target local repository
For example:
jf uv publish --publish-url=https://mycompany.jfrog.io/artifactory/api/pypi/pypi-local --build-name=my-app --build-number=42A --publish-url argument takes precedence over publish-url in pyproject.toml.
Important Notes
- After collecting build-info locally, publish it to Artifactory with
jf rt build-publish <build-name> <build-number>(aliasjf rt bp). The CLI must be able to authenticate to the server you publish to; if you see 401 Unauthorized, update credentials withjf config add --overwrite <server-id>or your CI secret rotation process, then run the command again. - For dependency commands (
sync,install,lock,add,remove,run), SHA256 checksums come fromuv.lock; SHA1 and MD5 are filled from Artifactory when the index URL host matches the server used for enrichment. The index host is resolved from the first[[tool.uv.index]]entry inpyproject.toml, or — when no such entry is configured — from theUV_DEFAULT_INDEXorUV_INDEX_URLenvironment variables. This means CI workflows that set those env vars instead of usingpyproject.tomlentries will also benefit from checksum enrichment. If the resolved host differs from the configured server host, you may see a warning and enrichment will likely return no results (the AQL query runs against the configured server but the packages are not there), unless you pass--server-idfor the correct instance. - For
publish, artifact lookup andbuild.name/build.number/build.timestampproperties on uploaded files require a publish URL whose host matches the configured Artifactory host (or use explicitUV_PUBLISH_*/ token variables). - Editable workspace packages are not listed as dependencies in build-info (same idea as other Python integrations).
- An unknown or mis-typed
--server-id(no matching entry fromjf config) produces a warning and JFrog CLI does not inject credentials from config;uvmay still succeed if native env vars, URLs, or.netrcalready authenticate your indexes, or it may fail (for example with 401).
Native Mode
This integration runs uv directly (no jf uv-config and no .jfrog/projects/*.yaml). Server connection and credentials come from jf config and optional --server-id. For background on native execution versus wrapped build tools, see Native Mode.
Environment Variables
| Variable | Default | Description |
|---|---|---|
JFROG_CLI_BUILD_NAME | (none) | When set with JFROG_CLI_BUILD_NUMBER, enables build-info collection without passing --build-name / --build-number on the command line |
JFROG_CLI_BUILD_NUMBER | (none) | Companion to JFROG_CLI_BUILD_NAME |
JFROG_CLI_BUILD_PROJECT | (none) | Same effect as --project when not passed on the command line |
UV_INDEX_<NAME>_USERNAME / UV_INDEX_<NAME>_PASSWORD | (none) | Native uv variables for named indexes; if already set, JFrog CLI does not overwrite them |
UV_PUBLISH_TOKEN or UV_PUBLISH_USERNAME / UV_PUBLISH_PASSWORD | (none) | Native uv publish authentication; take precedence over JFrog CLI injection for publish |
UV_KEYRING_PROVIDER | (uv default) | Set to disabled when the CLI injects username/password for indexes or publish |
NETRC | (none) | Optional path to a netrc file used when detecting netrc-based credentials |
CI/CD Example (GitHub Actions)
name: uv-build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: jfrog/setup-jfrog-cli@v4
env:
JF_URL: ${{ vars.JF_URL }}
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
- name: Sync with build-info
working-directory: ./my-python-app
run: |
jf uv sync --build-name=${{ github.repository }} --build-number=${{ github.run_number }}
- name: Publish build-info
run: jf rt build-publish ${{ github.repository }} ${{ github.run_number }}Replace ./my-python-app with your project path. Ensure pyproject.toml contains the appropriate [[tool.uv.index]] entries (or set UV_INDEX_* variables) so CI resolves from Artifactory.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No build-info after the command | --build-name / --build-number not both set, and env vars unset | Pass both flags, or set JFROG_CLI_BUILD_NAME and JFROG_CLI_BUILD_NUMBER, or use the same project build configuration mechanism as other JFrog CLI build tools (if configured) |
Error when only one of --build-name or --build-number is set | CLI validation | Always pass both together |
| 401 or 403 from Artifactory | Expired token, wrong user, or insufficient permissions | Run jf config add --overwrite <server-id> and confirm the user can read/write the target repos |
| 404 on resolve or publish | Repository or URL typo | Confirm virtual/local repo keys and full api/pypi/... paths |
| Warning: index host differs from jf server config host | [[tool.uv.index]] points at another host | Use --server-id for the matching instance, or set UV_INDEX_* credentials manually / netrc |
| Warning: publish URL host does not match jf server | Publish URL is on a different Artifactory than CLI config | Align URL and jf config, or set UV_PUBLISH_TOKEN / UV_PUBLISH_USERNAME / UV_PUBLISH_PASSWORD |
| Dependency checksums only have sha256 | No Artifactory index or host mismatch | Add a matching [[tool.uv.index]] and matching --server-id so AQL enrichment can run |
uv publish succeeds but build-info lacks artifact details | Publish URL host mismatch or missing repo key | Use a publish URL on the same host as the configured server, or rely on local dist/ fallback (see CLI logs) |
Command fails with no pyproject.toml | Not a uv project root | Run from the directory that contains pyproject.toml |
Enable debug logging: export JFROG_CLI_LOG_LEVEL=DEBUG
Related Topics
- Build Tools Overview — Capabilities matrix and tool reference
- Native Mode — Supported packages with Native Mode
jf pip—requirements.txtworkflowsjf poetry— Poetry andpyproject.tomlwith Poetry metadatajf pipenv— Pipfile-based projects
Updated about 17 hours ago
