How to Use Python Virtual Environments on RHEL 7
When you install Python packages system-wide with pip3, every project on the server shares the same set of libraries. This creates an immediate problem: Project A requires Flask 1.1 while Project B requires Flask 2.3, and only one version can occupy the global site-packages directory at a time. Python virtual environments solve this by giving each project its own isolated copy of the Python interpreter and its own independent package directory. Changes to one environment have no effect on any other. On RHEL 7, virtual environments are the standard way to manage Python dependencies for web applications, automation scripts, and development work, and they are an essential prerequisite to deploying applications with tools like Gunicorn, Celery, or Ansible.
Prerequisites
- RHEL 7 with Python 3 installed (see the previous tutorial on installing Python 3)
- Root or
sudoaccess for installing system packages python3-venvor the SCL equivalent installed (details in Step 1)- Basic shell familiarity
Step 1: Ensure the venv Module Is Available
The venv module is included in the Python standard library from Python 3.3 onward, but on RHEL 7 some package configurations install it as a separate sub-package. Check first:
python3 -m venv --help
If you see a “No module named venv” error, install the missing package:
# For SCL rh-python36
sudo yum install -y rh-python36-python-virtualenv
# For IUS python36u
sudo yum install -y python36u-virtualenv
# Alternatively, install virtualenv via pip3 (works with any installation method)
pip3 install --user virtualenv
The virtualenv package (installable via pip) is a superset of the built-in venv module. It supports Python 2 environments as well, and historically offered more features, but for Python 3.3+ projects the built-in venv is sufficient and requires no extra installation.
Step 2: Create a Virtual Environment
Navigate to your project directory and create a virtual environment. The conventional name for the environment directory is venv or .venv, though any name works.
# Create your project directory
mkdir -p ~/projects/myapp
cd ~/projects/myapp
# Create the virtual environment in a subdirectory named 'venv'
python3 -m venv venv
This creates the following structure inside the venv/ directory:
venv/
├── bin/
│ ├── activate # Bash/Zsh activation script
│ ├── activate.csh # C-shell activation script
│ ├── activate.fish # Fish shell activation script
│ ├── pip # pip linked to this environment
│ ├── pip3
│ ├── python # Symlink to the Python interpreter
│ └── python3
├── include/
├── lib/
│ └── python3.6/
│ └── site-packages/ # Packages installed in this venv land here
└── pyvenv.cfg
The pyvenv.cfg file records the Python version and base interpreter path. If you need to specify a particular Python binary (e.g., when multiple Python 3 versions are installed):
# Create environment using a specific Python binary
/opt/rh/rh-python38/root/usr/bin/python3.8 -m venv venv38
Step 3: Activate the Virtual Environment
Activating a virtual environment modifies your current shell’s PATH to put the environment’s bin/ directory first. After activation, python, python3, and pip all refer to the environment’s copies, not the system ones.
source venv/bin/activate
Your shell prompt changes to indicate the active environment:
(venv) [user@rhel7 myapp]$
Confirm that the environment’s Python is active:
which python
# /home/user/projects/myapp/venv/bin/python
python --version
# Python 3.6.12
which pip
# /home/user/projects/myapp/venv/bin/pip
Inside an activated virtual environment, you can use python and pip without version suffixes, and everything operates within the isolated environment.
Step 4: Install Packages Inside the Environment
With the environment activated, any pip install command installs packages only into this environment’s site-packages. No sudo is required.
# Upgrade pip first (always a good practice)
pip install --upgrade pip
# Install packages
pip install requests flask gunicorn
# List installed packages (only shows packages in this venv)
pip list
Packages installed here are completely invisible to the system Python and to other virtual environments:
# Verify isolation: check that flask is available here
python -c "import flask; print(flask.__version__)"
# Deactivate and verify it is NOT available system-wide
deactivate
python3 -c "import flask" 2>&1
# ModuleNotFoundError: No module named 'flask'
Step 5: Deactivate the Environment
To leave the virtual environment and return to the system Python:
deactivate
Your prompt returns to normal and python3, pip3 etc. refer to the system versions again. The environment directory is not deleted — it stays in place for you to reactivate later.
Step 6: Manage Dependencies with requirements.txt
A requirements.txt file is the standard way to record and reproduce a project’s exact dependencies. It lists every installed package and its pinned version.
Generate a requirements file
source venv/bin/activate
pip freeze > requirements.txt
cat requirements.txt
Example output:
click==8.0.3
Flask==2.0.2
gunicorn==20.1.0
itsdangerous==2.0.1
Jinja2==3.0.3
MarkupSafe==2.0.1
requests==2.26.0
Werkzeug==2.0.2
Reproduce the environment on another server
# On the target server:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Separating development and production dependencies
It is common practice to maintain separate requirements files for development tools (testing libraries, linters, debuggers) that should not be installed in production:
# requirements.txt — production dependencies only
Flask==2.0.2
gunicorn==20.1.0
requests==2.26.0
# requirements-dev.txt — includes production + development tools
-r requirements.txt
pytest==7.0.0
black==22.3.0
flake8==4.0.1
# Install production deps only
pip install -r requirements.txt
# Install dev deps (includes production)
pip install -r requirements-dev.txt
Step 7: virtualenv vs venv — Which to Use
The built-in venv module (available since Python 3.3) is the modern standard. The older virtualenv package was the original tool before venv was added to the standard library. In practice:
- Use
python3 -m venvfor all new Python 3 projects — it requires no extra installation and is maintained as part of Python itself. - Use
virtualenvonly if you need to create Python 2 environments, or if you need features specific tovirtualenvsuch as--copies(copy instead of symlink binaries) or cross-version environment creation. - Both produce compatible directory structures;
source venv/bin/activateworks with either.
# Using virtualenv explicitly (if installed)
virtualenv -p python3 venv
# Using the built-in venv
python3 -m venv venv
Step 8: Best Practices for Project Isolation
Following these conventions will keep your Python environments clean and reproducible across team members and deployment targets:
- One venv per project: Never share a virtual environment between unrelated projects.
- Never commit the venv directory: Add
venv/and.venv/to your.gitignore. Therequirements.txtfile is what gets committed. - Always pin versions in requirements.txt: Use
pip freeze(which pins exact versions) rather than writing approximate version specifiers likeFlask>=2.0for production deployments. - Use
.venv/as the directory name: The leading dot hides it from directory listings and is increasingly the community convention. - Document the Python version required: Include a
.python-versionfile or note in your README which Python version the environment was created with. - Recreate rather than repair: If a virtual environment becomes corrupted or confused, delete the directory and recreate it from
requirements.txtrather than trying to repair it in-place.
# .gitignore entry
echo "venv/" >> .gitignore
echo ".venv/" >> .gitignore
Python virtual environments on RHEL 7 are the foundation of clean, reproducible Python deployments. By creating an isolated environment per project, pinning dependencies in requirements.txt, and following the discipline of activating the correct environment before any pip or python command, you eliminate entire categories of dependency conflicts and deployment failures. Whether you are running a Flask web application, a Celery worker, a data processing script, or any other Python workload, virtual environments ensure that each piece of software has exactly the dependencies it needs — nothing more, nothing less.