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.12-slim)
silo install python
# Specific version
silo install python@3.11
Registry versions available today: 3.12, 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 python --shim pip -- 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.
# .siloconf — give pip network access
cat > .siloconf <<'EOF'
tools: [python]
overrides:
python:
network:
hostAccess: true
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 .siloconf in the project root
silo build python -- pip install -r requirements.txt
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 python --global -- 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 python@3.11 # default python changes to 3.11
Silo refuses a second silo install python unless you --force. The registry key is python, so installing python@3.11 replaces the global definition.
Pinning a version for one project (recommended)
silo use is the pyenv/asdf-style verb. It writes to .siloconf but does not install:
cd ~/projects/legacy-api
silo use python@3.11
# Writes to .siloconf:
# 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 .siloconf by hand:
# .siloconf
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/siloconf is a user-level .siloconf that applies whenever no project config exists (or gets merged under project config). Good place for personal defaults without touching per-project files:
# ~/.silo/siloconf
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 .siloconf:
overrides:
python:
network:
hostAccess: true
proxy:
allow:
- pypi.org
- "*.pythonhosted.org"
For a private index add its host:
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
.siloconf:
overrides:
jupyter:
network:
hostAccess: true
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
network:
hostAccess: true
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. (Registry defaults give this tool 2 GiB rootfs / 512 MiB RAM — if you outgrow that, bump the values in internal/tools/registry.yaml and silo install --force; those fields aren’t overridable from .siloconf today.)
Migrating from pyenv
If you already use pyenv, the mental model translates cleanly:
| pyenv | Silo |
|---|---|
pyenv install 3.11 | silo install python@3.11 |
pyenv local 3.11 | silo use python@3.11 |
pyenv global 3.12 | silo install python@3.12 --force |
.python-version | .siloconf (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.