Projects & Workers

A project is a single [[project]] entry in project.toml. A worker is the running process that Pypen spawns from that entry. The worker/ package is responsible for the full lifecycle of every worker.

Worker Lifecycle

  1. Plan. worker/config_loader.py parses project.toml, applies defaults, and produces a normalised list of project specs.
  2. Provision. worker/pyenv_utils.py ensures the requested Python version is available; uv creates a fresh venv at ./.venvs/<id>/.
  3. Clone. worker/project_manager.py clones the repo (with token injection for private repos) into ./projects/<id>/.
  4. Install. If the repo has a requirements.txt / pyproject.toml, uv pip install populates the venv.
  5. Generate service. worker/s6_config.py writes a run script under /etc/s6/services/<id>/ with the correct env vars and venv-aware exec line.
  6. Start. worker/s6_svc.py calls s6-svc -u to bring the service up.
  7. Watch. If the worker exits, s6-overlay restarts it. The dashboard shows the new state via Socket.IO.

Isolation

Every project gets its own:

  • Working directory./projects/<id>/
  • Python interpreter — via pyenv
  • Virtual environment — via uv
  • Environment variables — from [project.env]
  • Log directory/var/log/s6/<id>/ with bounded rotation
  • s6 service definition/etc/s6/services/<id>/

This means two projects can run wildly different stacks — one on Python 3.9 with pyrogram, another on Python 3.13 with aiohttp — in the same container, without ever touching each other.

Running Many Forks of the Same Repo

A common pattern is to spin up several copies of the same bot with different env vars. Just give each one a unique id:

[[project]]
id          = "tg-bot-a"
git_url     = "https://github.com/example/tg-bot"
branch      = "main"
run_command = "bot.py"
[project.env]
BOT_TOKEN = "TOKEN_A"

[[project]]
id          = "tg-bot-b"
git_url     = "https://github.com/example/tg-bot"
branch      = "main"
run_command = "bot.py"
[project.env]
BOT_TOKEN = "TOKEN_B"

Private Repositories

Set repo = "private" and provide an access_token (per-project, or globally in [defaults]). Pypen rewrites the clone URL to https://x-access-token:<token>@github.com/… so that git can authenticate without an SSH key.