Skip to main content
Integrate uv into your GitLab CI/CD pipelines for efficient Python dependency management.

Using uv Docker Images

1

Select a base image

Use one of the official uv Docker images. You can choose from distroless, Alpine, or Debian-based images:
variables:
  UV_VERSION: "0.10.8"
  PYTHON_VERSION: "3.12"
  BASE_LAYER: trixie-slim
  # GitLab CI creates separate mountpoints, use copy mode
  UV_LINK_MODE: copy

test:
  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
  script:
    - uv sync --locked
    - uv run pytest
2

For distroless images

When using distroless images, specify the entrypoint:
test:
  image:
    name: ghcr.io/astral-sh/uv:0.10.8
    entrypoint: [""]
  script:
    - uv --version

Pipeline Configuration

Here’s a complete example pipeline:
variables:
  UV_VERSION: "0.10.8"
  PYTHON_VERSION: "3.12"
  BASE_LAYER: trixie-slim
  UV_LINK_MODE: copy

stages:
  - lint
  - test
  - build

lint:
  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
  script:
    - uv sync --locked
    - uv run ruff check .

test:
  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
  script:
    - uv sync --locked --all-extras
    - uv run pytest tests --cov
  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'

build:
  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
  script:
    - uv build
  artifacts:
    paths:
      - dist/

Caching Dependencies

Persist the uv cache between pipeline runs to improve performance:
variables:
  UV_CACHE_DIR: .uv-cache
  UV_LINK_MODE: copy

test:
  image: ghcr.io/astral-sh/uv:0.10.8-python3.12-trixie-slim
  cache:
    - key:
        files:
          - uv.lock
      paths:
        - $UV_CACHE_DIR
  script:
    - uv sync --locked
    - uv run pytest
  after_script:
    - uv cache prune --ci
Use uv cache prune --ci to reduce cache size. See the cache documentation for more details.

Advanced Caching

Cache based on both lockfile and Python version:
variables:
  UV_CACHE_DIR: .uv-cache

test:
  cache:
    - key:
        files:
          - uv.lock
        prefix: $PYTHON_VERSION
      paths:
        - $UV_CACHE_DIR
  parallel:
    matrix:
      - PYTHON_VERSION: ["3.10", "3.11", "3.12"]
  image: ghcr.io/astral-sh/uv:0.10.8-python$PYTHON_VERSION-trixie-slim
  script:
    - uv sync --locked
    - uv run pytest
  after_script:
    - uv cache prune --ci

Matrix Testing

Test across multiple Python versions:
test:
  parallel:
    matrix:
      - PYTHON_VERSION: ["3.10", "3.11", "3.12"]
        BASE: ["trixie-slim", "alpine"]
  image: ghcr.io/astral-sh/uv:0.10.8-python$PYTHON_VERSION-$BASE
  script:
    - uv sync --locked
    - uv run pytest tests

Using uv pip

For workflows using the uv pip interface, enable system Python:
variables:
  UV_SYSTEM_PYTHON: 1

test:
  image: ghcr.io/astral-sh/uv:0.10.8-python3.12-trixie-slim
  script:
    - uv pip install -r requirements.txt
    - python -m pytest
When using uv pip, consider using requirements.txt or pyproject.toml instead of uv.lock in your cache key.

Installing uv in Custom Images

If you need to use a custom base image:
test:
  image: python:3.12-slim
  before_script:
    # Copy uv from official image
    - apt-get update && apt-get install -y curl
    - curl -LsSf https://astral.sh/uv/0.10.8/install.sh | sh
    - export PATH="/root/.local/bin:$PATH"
  script:
    - uv sync --locked
    - uv run pytest

Complete Example with Multiple Stages

variables:
  UV_VERSION: "0.10.8"
  PYTHON_VERSION: "3.12"
  UV_CACHE_DIR: .uv-cache
  UV_LINK_MODE: copy

stages:
  - check
  - test
  - build
  - deploy

.base:
  image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-trixie-slim
  cache:
    - key:
        files:
          - uv.lock
      paths:
        - $UV_CACHE_DIR
  before_script:
    - uv sync --locked

format:
  extends: .base
  stage: check
  script:
    - uv run ruff format --check .

lint:
  extends: .base
  stage: check
  script:
    - uv run ruff check .

type-check:
  extends: .base
  stage: check
  script:
    - uv run mypy .

test:
  extends: .base
  stage: test
  parallel:
    matrix:
      - PYTHON_VERSION: ["3.10", "3.11", "3.12"]
  script:
    - uv run pytest tests --cov --cov-report=xml
  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml
  after_script:
    - uv cache prune --ci

build:
  extends: .base
  stage: build
  script:
    - uv build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  only:
    - tags

publish:
  extends: .base
  stage: deploy
  script:
    - uv publish --token $PYPI_TOKEN
  only:
    - tags
  when: manual

Best Practices

  • Pin versions: Always specify exact versions for UV_VERSION and PYTHON_VERSION
  • Use cache: Enable caching to speed up pipeline runs
  • Set UV_LINK_MODE: Use copy mode since GitLab creates separate mountpoints
  • Prune cache: Run uv cache prune --ci in after_script to optimize cache size
  • Cache key: Use uv.lock as the cache key for projects, requirements.txt for pip workflows

Build docs developers (and LLMs) love