Python with Silo: install, persist, pin versions

Everything you need to run Python under Silo — installing pip packages so they survive, switching versions per project, and configuring network access for PyPI.

This is the long-form guide for Python under Silo. Three topics:

  1. Running Python and understanding what’s persistent vs. what isn’t.
  2. Making pip install stick via silo build.
  3. Using different Python versions, globally or per project.

Prerequisites: Silo installed, ~/.silo/bin on your PATH. If you haven’t done that, start with Getting started.

silo init → silo build python pip install -r requirements.txt → silo python app.py

Install Python

# Default (3.14-slim)
silo install python

# Specific version
silo install [email protected]

Registry versions available today: 3.14 (latest, default), 3.13, 3.12 (LTS), 3.11, 3.10, 3.9 (all -slim variants). If you try to install twice, Silo refuses — use --force for a true reinstall, or see the “multiple versions” section below.

After install, the python, python3, pip, and pip3 shims appear in ~/.silo/bin/. Because that directory is on your PATH, python script.py transparently routes through Silo.

Running Python

python --version
python -c "print('hello from the VM')"
python script.py
python -m http.server 8000

Interactive:

silo shell python       # shell inside the VM
python -i script.py     # REPL after running a script

Tool shorthand also works:

silo python script.py     # == silo run python script.py
silo pip install requests # == silo run --shim pip python install requests

What persists and what doesn’t

Every silo run boots a fresh micro-VM. That’s where the isolation comes from — nothing accumulates across runs unless you ask for it. The rules:

ThingPersists?Where
Files in the project directoryYes — mounted read-writeHost disk
Files outside the projectNo — the VM can’t see them
pip install target (site-packages)No by defaultEphemeral VM
pip download cacheYes — automatic mount~/.silo/cache/python/pip
Stdout / exit codeReturns to the host

So pip install requests && python -c "import requests" works inside a single run (shell or script), but pip install requests in one command and python -c "import requests" in the next will fail. The second run starts from a fresh rootfs.

Two ways to make installs stick: virtual environments in the project, or silo build to bake packages into the rootfs.

Option 1: virtualenv inside the project

Because the project directory is mounted read-write, a venv created there persists just like any other file.

# silo.toml — give pip network access
cat > silo.toml <<'EOF'
tools = ["python"]

[overrides.python.network]
hostAccess = true

[overrides.python.network.proxy]
allow = ["pypi.org", "*.pythonhosted.org"]
EOF

# Create the venv in the project, inside the VM
python -m venv .venv

# Activate + install — the venv lives on host disk, mounted into every run
source .venv/bin/activate   # on host? no — see below
pip install -r requirements.txt

One subtlety: a virtualenv’s bin/activate hardcodes absolute paths, and those paths are container paths (/workspace/.venv/...). Running source .venv/bin/activate on your host won’t do anything useful. Stay inside silo shell python, or run commands via the python shim which resolves the venv automatically inside the VM.

Practical pattern: use the venv’s interpreter directly.

python .venv/bin/pytest
python .venv/bin/uvicorn app:main

Option 2: persist with silo build

If you don’t want a venv, silo build bakes packages directly into a rootfs layer. Future runs start from that layer instead of the stock python:3.12-slim image.

# Requires a silo.toml in the project root
silo build python pip install -r requirements.txt

(The legacy silo build python -- pip install -r requirements.txt form with the -- separator is still accepted.)

This:

  1. Clones the normal rootfs.
  2. Runs pip install -r requirements.txt inside it.
  3. Saves the resulting filesystem as .silo/python/rootfs.ext4 in your project.
  4. Uses that on every subsequent silo run python for this project.

Future runs start with everything already installed:

python -c "import pandas; print(pandas.__version__)"   # no pip install needed

silo setup is kept as a deprecated alias of silo build in 0.4.x — both work. setup will be removed in 0.6.

Project-local vs global

By default, silo build writes to .silo/python/rootfs.ext4 in your project. Pass --global to put the layer in ~/.silo/builds/python/rootfs.ext4 for all projects:

# Global — poetry available in every project
silo build --global python pip install poetry

# Project-local stacks on top of global (sees poetry *and* the project's deps)
silo build python poetry install

Lookup order when a tool runs: project rootfs → global build rootfs → rootfs cache → OCI unpack.

Iterating on a build

silo build python --rerun                   # re-run the stored pip install (fresh dep versions)
silo build python --rerun --script "pip install -U pip && pip install -r requirements.txt"
silo build --all --rerun                    # for every tool that has a stored script
silo build python --remove                  # throw away the layer, back to stock

When to use which

Using different Python versions

Three cases:

Installing an additional version globally

silo install [email protected]       # default python changes to 3.11

Silo refuses a second silo install python unless you --force. The registry key is python, so installing [email protected] replaces the global definition.

silo use is the pyenv/asdf-style verb. It writes to silo.toml but does not install:

cd ~/projects/legacy-api
silo use [email protected]

# Writes to silo.toml:
# tools = ["python"]
# [overrides.python]
# image = "docker.io/library/python:3.11-slim"

silo sync       # install anything missing + warm the rootfs cache
python --version   # Python 3.11.x — only inside this project

Outside the project, the global version takes over. Walk into a different project and you’ll get whatever it pins (or the default).

Undo with:

silo unuse python

Manual override

If you’d rather not use the silo use verb, edit silo.toml by hand:

# silo.toml
[overrides.python]
image = "docker.io/library/python:3.10-slim"

Any valid image tag works — Silo doesn’t care whether it’s a registry “version” or a custom one.

Project-and-user overrides

~/.silo/silo.toml is a user-level silo.toml that applies whenever no project config exists (or gets merged under project config). Good place for personal defaults without touching per-project files:

# ~/.silo/silo.toml
[overrides.python]
env = { PYTHONDONTWRITEBYTECODE = "1", PYTHONUNBUFFERED = "1" }

Networking for pip

By default the VM has no network access. pip install will fail. Enable PyPI in silo.toml:

[overrides.python.network]
hostAccess = true

[overrides.python.network.proxy]
allow = ["pypi.org", "*.pythonhosted.org"]

For a private index add its host:

[overrides.python.network.proxy]
allow = ["pypi.org", "*.pythonhosted.org", "pypi.mycompany.com"]

The proxy is allowlist-first: only the domains you name can be reached, everything else is denied. This means a compromised package’s setup.py that tries to POST your env vars to attacker.example gets blocked at the network layer.

Forwarding ports for dev servers

Running manage.py runserver or uvicorn? Map the port so the host can reach it:

[[overrides.python.ports]]
host = 8000
guest = 8000

Then python -m http.server 8000 inside the VM is reachable on http://localhost:8000 on the host. Ports imply hostAccess = true.

Tip: silo config ports add python 8000:8000 does the same thing from the CLI.

Passing env vars and secrets

Env vars don’t leak into the VM by default. Opt them in:

passEnv = ["GITHUB_TOKEN", "DATABASE_URL", "SENTRY_DSN"]

And if you need a host file (.pypirc, ~/.netrc) mounted read-only:

passFiles = [".pypirc"]

Recipes

Jupyter

silo install jupyter          # requires python
silo build jupyter pip install pandas matplotlib scikit-learn

silo.toml:

[overrides.jupyter.network]
hostAccess = true

[[overrides.jupyter.ports]]
host = 8888
guest = 8888

Then jupyter lab --ip=0.0.0.0 inside the VM is reachable at http://localhost:8888.

Data science environment

[overrides.python]
image = "docker.io/library/python:3.11-slim"
cpus = 4
memoryMB = 8192              # numpy/pandas/torch dataframes need real RAM
rootfsSizeMB = 8192          # torch alone is > 2 GB on disk

[overrides.python.network]
hostAccess = true

[overrides.python.network.proxy]
allow = ["pypi.org", "*.pythonhosted.org", "download.pytorch.org"]

Pair with silo build python pip install numpy pandas torch once; after that every run is instant. The registry defaults are conservative (2 GiB rootfs / 512 MiB RAM) so they fit on small disks — for ML or scientific work, bump memoryMB and rootfsSizeMB per project as shown above. Verify with silo current python.

Pyright LSP — pin a version, log verbosely

The python tool ships a default pyright install via silo lsp python. To pin a specific pyright version (e.g. to match a CI image) or tweak the LSP behaviour without forking the registry, override the lsp block:

[overrides.python.lsp]
install = "npm i -g [email protected]"
env = { PYRIGHT_LOG = "verbose" }

[[overrides.python.lsp.cache]]
guest = "/root/.cache/pyright"
host = "~/silo-cache/pyright"

silo lsp python (or your editor’s IDE config from silo ide vscode) will pick up the pinned version on the next start. command is replaced wholesale; env deep-merges per key onto the registry default; cache mounts dedup by guest path, so a host-side cache directory survives across rebuilds.

Scoping ANTHROPIC_API_KEY to claude-code only

A common pitfall: putting passEnv = ["ANTHROPIC_API_KEY"] at the top of silo.toml means every tool you run sees the key — including a silo run python shell where a malicious package’s setup.py could exfiltrate it. Scope it to one tool:

tools = ["python", "claude-code"]
passEnv = ["GITHUB_TOKEN"]                   # forwarded to all tools

[overrides.claude-code]
passEnv = ["ANTHROPIC_API_KEY"]              # forwarded to claude-code only

silo run python won’t see ANTHROPIC_API_KEY; silo run claude-code does.

Migrating from pyenv

If you already use pyenv, the mental model translates cleanly:

pyenvSilo
pyenv install 3.11silo install [email protected]
pyenv local 3.11silo use [email protected]
pyenv global 3.12silo install [email protected] --force
.python-versionsilo.toml (overrides.python.image)

You get version switching and filesystem isolation in the same tool.

Where to go next

← all posts