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:
- Running Python and understanding what’s persistent vs. what isn’t.
- Making
pip installstick viasilo build. - 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.
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:
| Thing | Persists? | Where |
|---|---|---|
| Files in the project directory | Yes — mounted read-write | Host disk |
| Files outside the project | No — the VM can’t see them | — |
pip install target (site-packages) | No by default | Ephemeral VM |
| pip download cache | Yes — automatic mount | ~/.silo/cache/python/pip |
| Stdout / exit code | Returns 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:
- Clones the normal rootfs.
- Runs
pip install -r requirements.txtinside it. - Saves the resulting filesystem as
.silo/python/rootfs.ext4in your project. - Uses that on every subsequent
silo run pythonfor this project.
Future runs start with everything already installed:
python -c "import pandas; print(pandas.__version__)" # no pip install needed
silo setupis kept as a deprecated alias ofsilo buildin 0.4.x — both work.setupwill 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
- Virtualenv in the project when deps change often in development. Edit
requirements.txt,pip install -r requirements.txtinsidesilo shell python, commit the lockfile. silo build(project-local) for reproducibility — CI or teammates can get the same environment withsilo sync+silo build --rerun.silo build --globalfor tools you want everywhere (poetry,uv,pipx,black).
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.
Pinning a version for one project (recommended)
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:
| pyenv | Silo |
|---|---|
pyenv install 3.11 | silo install [email protected] |
pyenv local 3.11 | silo use [email protected] |
pyenv global 3.12 | silo install [email protected] --force |
.python-version | silo.toml (overrides.python.image) |
You get version switching and filesystem isolation in the same tool.
Where to go next
- Node.js with Silo — same patterns for the JS ecosystem.
- How Silo works — what’s actually happening under
silo run. - v0.4.0 release notes — why the command is
silo buildnow.