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 with uv --version.
  • JFrog CLI installed and authenticated — install or upgrade from the JFrog CLI quick start, then configure a server with jf config add or jf 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 with jf 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_HOME if 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.toml should reference your virtual or remote repository (for example https://<artifactory-host>/artifactory/api/pypi/<virtual-repo>/simple).
  • Project layout — build-info collection expects a valid uv project; operations that need pyproject.toml fail if it is missing.

Build: jf uv

jf 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

ArgumentRequiredDescription
<uv-subcommand>YesFirst 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

FlagDefaultDescription
--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-idDefault serverJFrog 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:

  1. Add [[tool.uv.index]] entries in pyproject.toml with a name and url pointing at Artifactory.
  2. Optionally set publish-url under [tool.uv] for uv publish.
  3. Ensure jf config (or --server-id) points at the same Artifactory host when you want the CLI to inject UV_INDEX_<NAME>_USERNAME / UV_INDEX_<NAME>_PASSWORD for 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-virtualPYPI_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).

Helpjf uv --help and jf uv <subcommand> --help invoke uv directly.

Example 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 sync

Where:

  • <project-root> is the directory that contains pyproject.toml

For example:

cd ~/projects/my-app
jf uv sync

On 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=42

Explicit Artifactory server

jf uv sync --build-name=<build-name> --build-number=<build-number> --server-id=<server-id>

Where:

  • <server-id> is the server alias from jf config add

For example:

jf uv sync --build-name=my-app --build-number=42 --server-id=artifactory-prod

Lock 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=42

Add, 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 by uv add / uv remove
  • <command> is the program and arguments passed to uv 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=42

Build 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=42

When 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 subcommandDependencies in build-infoArtifacts in build-info
sync, install, lock, add, remove, runYesNo
buildCleared (module ID only)No
publishUnchanged from prior stepsYes (.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:

  1. jf uv sync — resolves and records dependencies in build-info
  2. jf uv build — builds the distribution; records only the module ID
  3. jf uv publish — uploads artifacts and records them in build-info

Publish: jf uv publish

Publishing uses the same jf uv entrypoint with the publish subcommand.

Before you publish

  1. Run jf uv build so dist/ contains .whl and .tar.gz. jf uv publish fails if dist/ is missing or empty.
  2. Set publish-url to 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

ArgumentRequiredDescription
publishYesLiteral first argument selecting uv publish
Remaining argsNoAny 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=42

Publish 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=42

A --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> (alias jf rt bp). The CLI must be able to authenticate to the server you publish to; if you see 401 Unauthorized, update credentials with jf 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 from uv.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 in pyproject.toml, or — when no such entry is configured — from the UV_DEFAULT_INDEX or UV_INDEX_URL environment variables. This means CI workflows that set those env vars instead of using pyproject.toml entries 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-id for the correct instance.
  • For publish, artifact lookup and build.name / build.number / build.timestamp properties on uploaded files require a publish URL whose host matches the configured Artifactory host (or use explicit UV_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 from jf config) produces a warning and JFrog CLI does not inject credentials from config; uv may still succeed if native env vars, URLs, or .netrc already 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

VariableDefaultDescription
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

SymptomCauseFix
No build-info after the command--build-name / --build-number not both set, and env vars unsetPass 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 setCLI validationAlways pass both together
401 or 403 from ArtifactoryExpired token, wrong user, or insufficient permissionsRun jf config add --overwrite <server-id> and confirm the user can read/write the target repos
404 on resolve or publishRepository or URL typoConfirm virtual/local repo keys and full api/pypi/... paths
Warning: index host differs from jf server config host[[tool.uv.index]] points at another hostUse --server-id for the matching instance, or set UV_INDEX_* credentials manually / netrc
Warning: publish URL host does not match jf serverPublish URL is on a different Artifactory than CLI configAlign URL and jf config, or set UV_PUBLISH_TOKEN / UV_PUBLISH_USERNAME / UV_PUBLISH_PASSWORD
Dependency checksums only have sha256No Artifactory index or host mismatchAdd a matching [[tool.uv.index]] and matching --server-id so AQL enrichment can run
uv publish succeeds but build-info lacks artifact detailsPublish URL host mismatch or missing repo keyUse 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.tomlNot a uv project rootRun from the directory that contains pyproject.toml

Enable debug logging: export JFROG_CLI_LOG_LEVEL=DEBUG

Related Topics