① Scenario fit: pip vs uv vs conda (when uv wins)
Use this matrix before you standardize a toolchain on shared remote Mac runners. It assumes Apple Silicon or Intel macOS build hosts and WAN pulls to PyPI or regional mirrors.
| Tool | Choose when | Trade-off on remote Mac CI |
|---|---|---|
| pip (+ venv) | Legacy scripts, minimal moving parts, vendor already standardized on requirements.txt |
Slower cold resolves; fewer first-class knobs for concurrent downloads than uv |
| uv | Fast resolves/installs, uv.lock for apps/services, mixed wheel+sdist graphs, ML stacks with heavy parallel fetches |
Team must learn uv sync/uv run and lockfile policy; mirror config moves to UV_* env and pyproject |
| conda/mamba | Binary stacks dominated by conda-forge (some scientific bundles), non-PyPI channels | Larger tarballs and different cache semantics; less overlap with pure PyPI mirror tuning in this article |
Rule of thumb for 2026 Python CI on MacPull-style nodes: if most dependencies are on PyPI and you want one lockfile plus aggressive parallel I/O, default to uv. Keep pip in thin wrapper jobs only when upstream docs mandate it.
② Executable parameter matrix (concurrency, timeout, cache, mirror)
These variables are read directly by uv (see the upstream environment reference). Tune them per link quality; raise timeouts before you raise concurrency on lossy cross-border paths.
| Knob | Typical CI value | Effect |
|---|---|---|
UV_CONCURRENT_DOWNLOADS |
8–32 (start 16) |
Cap in-flight wheel/sdist HTTP downloads |
UV_CONCURRENT_BUILDS |
1–4 |
Parallel source builds (watch CPU/RAM on small Mac nodes) |
UV_CONCURRENT_INSTALLS |
default or 4–8 |
Unzip/link parallelism into the venv |
UV_HTTP_TIMEOUT / HTTP_TIMEOUT |
120–300 |
Read timeout seconds for registry and file fetches |
UV_HTTP_CONNECT_TIMEOUT |
15–45 |
TCP/TLS connect phase; increase if handshake stalls |
UV_HTTP_RETRIES |
5–10 |
Transient 5xx/connection drops on long haul links |
UV_CACHE_DIR |
/var/ci/uv-cache or per-runner path |
Persistent warm cache between jobs (isolate pools if needed) |
UV_DEFAULT_INDEX |
https://pypi.org/simple or mirror URL |
Primary Simple API; preferred over legacy UV_INDEX_URL name |
UV_INDEX / extra indexes |
space-separated Simple URLs | Secondary mirrors or private indexes (combine with strategy flags carefully) |
Copy-paste block for a shell step (adjust mirror host to your approved endpoint):
export UV_CACHE_DIR="${UV_CACHE_DIR:-$HOME/Library/Caches/uv-ci}"
export UV_DEFAULT_INDEX="${UV_DEFAULT_INDEX:-https://pypi.org/simple}"
# Fallback mirror (example placeholder—replace with your org mirror):
# export UV_DEFAULT_INDEX="https://your-mirror.example.com/simple"
export UV_CONCURRENT_DOWNLOADS="${UV_CONCURRENT_DOWNLOADS:-16}"
export UV_CONCURRENT_BUILDS="${UV_CONCURRENT_BUILDS:-2}"
export UV_HTTP_TIMEOUT="${UV_HTTP_TIMEOUT:-180}"
export UV_HTTP_CONNECT_TIMEOUT="${UV_HTTP_CONNECT_TIMEOUT:-30}"
export UV_HTTP_RETRIES="${UV_HTTP_RETRIES:-8}"
uv sync --frozen --no-dev
pip-style job without a pyproject workspace:
uv venv .venv
source .venv/bin/activate
uv pip install -r requirements.txt \
--index-url "${UV_DEFAULT_INDEX:-https://pypi.org/simple}"
③ Cross-border failures: TLS, certificates, and proxies
Symptoms cluster into three buckets: handshake failures, MITM corporate roots, and proxy routing loops. Fix certificate trust first; only then chase higher UV_HTTP_RETRIES.
export SSL_CERT_FILE=/path/to/org-trust.pem. uv also honors SSL_CERT_DIR for hashed certificate folders.
export UV_NATIVE_TLS=true or export UV_SYSTEM_CERTS=true (newer uv) so the platform store participates in verification—still document the exact macOS image you bake into CI.
HTTPS_PROXY, HTTP_PROXY, and ALL_PROXY with your egress policy. Use NO_PROXY for on-prem indexes, metadata endpoints, or link-local health checks.
export HTTPS_PROXY="http://proxy.example.com:8080" export NO_PROXY="localhost,127.0.0.1,.internal,private-index.example.com" # Optional: enterprise PEM export SSL_CERT_FILE="/etc/ssl/certs/org-bundle.pem"
For shared-runner contention patterns (not Python-specific), see pull stability FAQ and the Rust mirror/retry matrix—the same retry mindset applies across ecosystems.
④ Lockfiles, pyproject, and reproducible CI steps
Reproducibility is a contract between resolver inputs and the committed graph. On remote Mac CI, separate resolve/fetch from test so flaky WAN shows up early.
uv.lock for applications. Run uv lock on a developer machine or a lock refresh job, then commit the diff intentionally—never silently during unrelated PRs.
uv sync --frozen --no-dev (or UV_FROZEN=1) so the job cannot rewrite the lock. Use uv sync --locked when you want uv to verify the lock matches pyproject.toml and fail if drift exists.
requirements.txt, export from the lock: uv export --format requirements.txt --frozen --output-file requirements.lock (filename is organizational; --frozen keeps the export aligned with the committed uv.lock without re-resolving).
UV_REQUIRE_HASHES=true during install so any tampered artifact fails closed.
# Job A: fetch + install only uv sync --frozen --no-dev # Job B: tests assume env is warm uv run pytest -q
⑤ FAQ: cache pollution, platform wheels, private indexes
Cache pollution. Share one UV_CACHE_DIR per stable runner image key, or namespace by team. Schedule uv cache prune when disk crosses your watermark; combine with the concurrency guidance in build pool disk FAQ.
Platform wheels. Apple Silicon resolves macosx_11_0_arm64 tags; Intel runners need different wheels. If you generate locks on the wrong arch, CI rebuilds sdists or fails. Align runner architecture or use explicit platform flags when locking for heterogeneous pools.
Private + public PyPI. Put the private Simple API on UV_DEFAULT_INDEX when it should win, add PyPI through UV_INDEX or tool.uv.index entries, and avoid unsafe-best-match unless you understand cross-index version competition. Authenticate with UV_INDEX_{NAME}_USERNAME / UV_INDEX_{NAME}_PASSWORD for named indexes.
Structured FAQ for search engines mirrors the questions above (cache isolation, wheel tags, mixed indexes).
Summary
Pick uv when PyPI throughput and lockfile speed matter on a remote Mac; drive mirrors through UV_DEFAULT_INDEX and friends; raise UV_HTTP_TIMEOUT and UV_HTTP_RETRIES before you chase ever-higher UV_CONCURRENT_DOWNLOADS. Commit uv.lock and run uv sync --frozen in CI for deterministic installs.
When you need Apple Silicon close to your users with stable egress for large pulls, browse MacPull pricing and help & getting started—both are readable without logging in. Choose a node size that matches your parallel build+download footprint; oversized concurrency on a small VM only amplifies timeouts.
Continue the dependency story with Git, npm & CI cache strategy, then compare plans on the MacPull homepage and proceed to purchase when you are ready for a dedicated remote Mac.
Remote Mac for Python & ML CI
Dedicated Mac Mini nodes, SSH access, and room for heavy uv caches. View pricing, purchase, and help without signing in.