blumeops/docs/how-to/operations/devpi-on-indri.md
Erich Blume 14ca0160ba Migrate devpi from minikube to indri (launchd) (#341)
## Summary

Devpi was crash-looping under memory pressure on the minikube StatefulSet, breaking the Python toolchain across the repo (`mise run docs-mikado`, `prek`, every `uv pip install`). It moves to indri as a native LaunchAgent.

## What changed

- **New ansible role** `ansible/roles/devpi/`: installs `devpi-server` + `devpi-web` into a uv-managed venv, initializes the server-dir on first run via 1Password root password, runs as a LaunchAgent (`mcquack.eblume.devpi`) bound to `127.0.0.1:3141`. Bootstraps from upstream PyPI (so devpi can install itself on a fresh box).
- **Caddy**: `pypi.ops.eblu.me` now proxies to `http://localhost:3141`.
- **Playbook**: `indri.yml` gains pre_tasks for the root password and the new role.
- **service-versions.yaml**: devpi flipped from `type: argocd` to `type: ansible`.
- **ArgoCD**: removed `apps/devpi.yaml` and `manifests/devpi/`. The in-cluster Application, namespace, and PVC have been deleted.
- **Docs**: new how-to `docs/how-to/operations/devpi-on-indri.md`; `restart-indri.md` lists devpi in the LaunchAgent stop list.

## Already deployed (live on indri)

- Service running: `launchctl list mcquack.eblume.devpi` → PID 53888
- `curl https://pypi.ops.eblu.me/+api` returns 200 
- `mise run docs-mikado` works again 
- 1.0G of cached PyPI data was migrated from the PVC to `~erichblume/devpi/server-dir/`
- Minikube namespace and PVC fully reclaimed

## Test plan

- [ ] `mise run services-check` (after merge)
- [ ] CI workflows that use devpi succeed
- [ ] No regressions in tools that depend on `pypi.ops.eblu.me` (prek, uv-script tasks, dagger pipelines)

## Context

This is the C1 prelude to a planned C2 chain (`mikado/retire-minikube-indri`) to retire minikube on indri entirely. Doing devpi as a standalone C1 was the right call because (a) it was urgent — it was breaking the toolchain — and (b) it shakes out the migration recipe before we commit to a multi-leaf chain.

Reviewed-on: #341
2026-04-29 13:38:36 -07:00

2.9 KiB

title modified last-reviewed tags
Devpi on Indri 2026-04-29 2026-04-29
how-to
operations

Devpi on Indri

How devpi (the PyPI caching mirror at pypi.ops.eblu.me) is deployed on indri as a launchd-managed native service. Replaces the prior minikube StatefulSet.

Why native, not Kubernetes

Devpi has no runtime dependencies beyond a Python interpreter, a writable directory, and outbound HTTPS to upstream PyPI. Running it on indri natively removes a layer of operational complexity, frees minikube resources, and decouples this critical-path tooling (used by every Python build, including mise run docs-mikado itself) from cluster health.

Layout

Concern Path / detail
Service binary /Users/erichblume/devpi/venv/bin/devpi-server
Server-dir (data) /Users/erichblume/devpi/server-dir/
Logs /Users/erichblume/Library/Logs/mcquack.devpi.{out,err}.log
LaunchAgent label mcquack.eblume.devpi
LaunchAgent plist ~/Library/LaunchAgents/mcquack.eblume.devpi.plist
Listen address 127.0.0.1:3141 (loopback only)
Public URL https://pypi.ops.eblu.me (via Caddy reverse proxy)
Root password secret 1Password item devpi, field root password

The venv is built fresh by ansible from a pinned devpi-server and devpi-web version; bumping versions is a config change in ansible/roles/devpi/defaults/main.yml.

Deploy

mise run provision-indri -- --tags devpi

Ansible will:

  1. Fetch the root password from 1Password (in playbook pre_tasks)
  2. Create the venv at ~/devpi/venv if absent and install/upgrade devpi-server + devpi-web to the pinned versions
  3. Initialize the server-dir (only on first run, when .serverversion is missing)
  4. Render and load the LaunchAgent plist
  5. Restart the service if the plist or config changed

Caddy already proxies pypi.ops.eblu.me127.0.0.1:3141; nothing else routes traffic.

Verify

ssh indri 'launchctl list mcquack.eblume.devpi'
curl -fsS https://pypi.ops.eblu.me/+api | jq
uv pip install --index-url https://pypi.ops.eblu.me/root/pypi/+simple/ requests

Logs

ssh indri 'tail -f ~/Library/Logs/mcquack.devpi.err.log'

Bumping devpi versions

Edit devpi_server_version / devpi_web_version in ansible/roles/devpi/defaults/main.yml, then re-run the playbook with --tags devpi. The role rebuilds the venv in-place; the server-dir survives.

Backup

The server-dir is not in borgmatic_source_directories and is not backed up. The PyPI cache (+files/) is re-fetchable from upstream on first request; the local eblume/dev index can be republished from source. If retention becomes important, add /Users/erichblume/devpi/server-dir/ to the borgmatic source list.

  • restart-indri — devpi is one of the LaunchAgents to stop on graceful shutdown
  • connect-to-postgres — pattern for indri-native services (different stack, similar shape)