Last modified: Oct 02, 2026

Install Python Packages on Read-Only Filesystem

You try to run pip install requests. Instead of a download bar, you get an error.

The message says the filesystem is read-only. You cannot write to /usr/lib/python3 or any system path.

This is common on locked-down servers, containers, and embedded devices. It does not mean you are stuck.

This guide shows you several clean ways to install Python packages when the main filesystem refuses writes.

Why the Read-Only Filesystem Error Happens

Linux systems often mount root as read-only for safety. A crash cannot corrupt files that cannot be changed.

Containers like Docker do the same. They keep the image layer immutable and only allow writes to specific mounts.

When pip tries to write to site-packages, the kernel blocks it. Pip then exits with a permission or I/O error.

The fix is simple in concept. You redirect the install to a place that is writable.

Option 1: Use a Virtual Environment in a Writable Path

A virtual environment is the cleanest solution. It keeps packages separate from the system Python.

You need a writable directory. Common choices are /tmp, /home/youruser, or a mounted data volume.

Create the environment with the built-in venv module.


# Create a virtual environment in a writable directory
python3 -m venv /tmp/myenv

# Activate it
source /tmp/myenv/bin/activate

# Now pip writes inside /tmp/myenv, which is writable
pip install requests

Collecting requests
  Downloading requests-2.31.0-py3-none-any.whl (62 kB)
Installing collected packages: requests
Successfully installed requests-2.31.0

The install succeeds because every file lands inside /tmp/myenv. The read-only root is never touched.

Remember to activate the environment in every new shell. Add the activation line to your startup script if needed.

Option 2: Install to a Target Directory with pip

Sometimes you cannot use a virtual environment. Maybe you only need one package in a specific folder.

Pip has a --target flag for exactly this case. It installs packages into any directory you choose.


# Install a package into a custom writable folder
pip install --target=/tmp/mypackages requests

# Then tell Python where to find it at runtime
import sys
sys.path.insert(0, "/tmp/mypackages")

import requests
print(requests.__version__)

2.31.0

This works well for scripts and cron jobs. You just prepend the folder to sys.path before importing.

One downside is that pip will not manage dependencies across multiple target folders. Keep them organized.

Option 3: Use the User Site Directory

Pip can install packages for your user only. This avoids system paths entirely.

Use the --user flag. Packages go to ~/.local/lib/pythonX.Y/site-packages.


# Install for the current user only
pip install --user requests

# Python finds user packages automatically
import requests
print(requests.__version__)

2.31.0

This only works if your home directory is writable. On many read-only systems, it is not.

If $HOME is also read-only, combine this with a custom PYTHONUSERBASE variable.


# Point the user base to a writable folder
export PYTHONUSERBASE=/tmp/userbase

# Install into that custom base
pip install --user requests

Now the packages live in /tmp/userbase. Python still finds them without extra path edits.

Option 4: Use PYTHONPATH for Portable Installs

For maximum control, install to a folder and set PYTHONPATH. This tells Python where to look at runtime.

This is great for shared servers and CI pipelines. It keeps the environment self-contained.


# Install into a project folder
pip install --target=/opt/app/libs requests

# Export the path so Python sees it
export PYTHONPATH=/opt/app/libs:$PYTHONPATH

# Verify the import works
python3 -c "import requests; print(requests.__version__)"

2.31.0

This method requires no root access and no system changes. Everything stays inside your chosen folder.

Option 5: Build a Wheel and Copy It

What if the machine has no internet or no pip at all? Build the wheel elsewhere and copy it over.

On a writable machine, run pip download or pip wheel. Then move the file to the read-only host.


# On a machine with internet access
pip wheel requests -w /tmp/wheels

# Copy the wheel to the target machine
scp /tmp/wheels/requests-2.31.0-py3-none-any.whl user@host:/tmp/

# On the read-only machine, install from the local wheel
pip install --no-index --find-links=/tmp/wheels requests

Processing /tmp/wheels/requests-2.31.0-py3-none-any.whl
Installing collected packages: requests
Successfully installed requests-2.31.0

This approach is perfect for air-gapped systems. It also avoids any network calls during install.

Handling Dependencies and C Extensions

Some packages contain compiled C code. These need a compiler at build time.

If the read-only system lacks build tools, use wheels. Wheels are pre-built and need no compiler.

Always check for a manylinux wheel first. It works on almost every Linux system.

If a dependency fails, install it first with the same target flag. Then install the main package.

Common Pitfalls to Avoid

Do not mix system packages with target packages. Version conflicts will confuse Python.

Do not forget to set PYTHONPATH in every shell and service file. Missing paths cause import errors.

Do not install into /tmp for long-term use. Some systems clear /tmp on reboot.

Pick a persistent writable location for production. Use /opt, /srv, or a mounted volume.

Quick Comparison of Methods

Virtual environment: best for development and isolation.

--target: best for scripts and single-folder installs.

--user: best when home is writable and you want simplicity.

PYTHONPATH: best for shared servers and portable setups.

Wheels: best for offline and air-gapped machines.

Conclusion

A read-only filesystem is not a dead end. It just means you must choose where Python looks for packages.

Start with a virtual environment in a writable folder. It is the cleanest and safest option.

For edge cases, use --target, PYTHONUSERBASE, or a copied wheel. Each solves a specific problem.

Test your import after every change. A quick python3 -c "import package" saves hours of debugging.

With these methods, you can install Python packages anywhere, even on the most locked-down system.