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.

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:

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.

# .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:

  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 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

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.

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:

pyenvSilo
pyenv install 3.11silo install python@3.11
pyenv local 3.11silo use python@3.11
pyenv global 3.12silo 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

← all posts