Chọn venv, pipx hay uv trên Ubuntu server?
Lỗi externally-managed-environment trên Ubuntu 24.04 chặn pip install. Bài viết hướng dẫn chọn công cụ quản lý Python phù hợp và cách cấu hình systemd cho ứng dụng.
Tại sao pip install thất bại trên Ubuntu server mới
Việc chọn giữa Python venv, pipx và uv trên server phụ thuộc vào một câu hỏi: bạn đang cài đặt cái gì? Các dependency của ứng dụng thuộc về một virtual environment nằm trong thư mục của chính ứng dụng đó. Các công cụ CLI mà bạn muốn gọi bằng tên thuộc về pipx. uv thực hiện cả hai việc này và bổ sung thêm lockfile, điều này bắt đầu trở nên quan trọng ngay khi máy thứ hai cần build cùng một môi trường. Không công cụ nào trong số đó cài đặt vào Python của hệ thống, vì Ubuntu server hiện tại từ chối thẳng thừng việc này.
Chạy sudo pip install requests trên Ubuntu 24.04 và pip sẽ dừng lại trước khi tải xuống dù chỉ một file.
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip.
If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you.
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this behaviour by passing --break-system-packages.Đây là PEP 668 (Python enhancement proposal 668, "môi trường được quản lý bên ngoài") đang thực hiện nhiệm vụ của nó. Debian và Ubuntu đặt một file đánh dấu bên cạnh trình thông dịch tại /usr/lib/python3.12/EXTERNALLY-MANAGED, và pip từ chối ghi vào bất kỳ trình thông dịch nào có chứa file này.
Quy tắc này tồn tại vì thứ tự sys.path. apt cài đặt các thư viện vào /usr/lib/python3/dist-packages. pip, khi chạy dưới quyền root đối với trình thông dịch hệ thống, sẽ ghi vào /usr/local/lib/python3.12/dist-packages, và trình đóng gói của Debian đặt thư mục đó lên trước trong đường dẫn tìm kiếm. Hãy tự in nó ra bằng python3 -c 'import sys; print(sys.path)' và đọc thứ tự. Vì vậy, bản copy mà pip ghi đè lên bản copy mà apt đã cài đặt, đối với mọi chương trình trên máy chạy dưới /usr/bin/python3, bao gồm cả các công cụ của chính bản phân phối. cloud-init import requests, jinja2 và PyYAML từ trình thông dịch đó. Nâng cấp một trong số chúng bằng pip, gặp phải một bản release không tương thích, và thứ gì đó bạn chưa từng đụng đến sẽ lỗi ở lần boot tiếp theo với một traceback chỉ đích danh một package mà bạn không hề biết là có trong chuỗi. apt vẫn ghi nhận phiên bản của riêng nó là đã cài đặt, vì vậy không có gì cảnh báo bạn, và việc sửa chữa là sudo apt reinstall python3-requests.
Quy tắc cần tuân theo rất ngắn gọn. Python hệ thống thuộc về bản phân phối. Đừng cài đặt vào đó, đừng nâng cấp các thư viện của nó bằng pip, và đừng xóa file EXTERNALLY-MANAGED để làm mất thông báo lỗi. Công việc duy nhất cần giao cho /usr/bin/python3 là tạo các virtual environment.
venv, pipx và uv: quy tắc lựa chọn
Hãy chọn công cụ dựa trên những gì bạn đang cài đặt, thay vì công cụ bạn vừa đọc được gần đây nhất.
- Một ứng dụng bạn triển khai và chạy dưới dạng service, ví dụ như dự án Django hoặc Flask: sử dụng một virtual environment (venv) bên trong thư mục của ứng dụng đó.
- Một công cụ dòng lệnh (CLI) bạn muốn có trên
PATH, ví dụ nhưansiblehoặchttpie: dùng pipx, công cụ này tạo cho mỗi ứng dụng một môi trường riêng biệt và một liên kết trênPATH. - Một dự án cần file lock, tốc độ cài đặt nhanh, hoặc một phiên bản Python mà bản phân phối hệ điều hành không cung cấp: dùng uv, công cụ này tạo ra một venv thông thường kèm theo một file
uv.lock. - Một thư viện mà công cụ của hệ điều hành cần thay vì code của bạn:
sudo apt install python3-<name>, đây là cách duy nhất được hỗ trợ để thêm bất kỳ thứ gì vào trình thông dịch Python của hệ thống.
pipx và uv tool install thực hiện cùng một công việc, vì vậy một máy chủ đã có uv thì không cần thêm pipx nữa. Việc bạn chọn web framework nào không làm thay đổi điều này: Django và Flask trên VPS chỉ khác nhau ở những gì được đưa vào requirements.txt, chứ không khác nhau về cách xây dựng môi trường xung quanh nó. Mọi hướng dẫn dưới đây đều sử dụng Ubuntu 24.04 và Python 3.12 đi kèm, vì vậy hãy điều chỉnh phiên bản trong các đường dẫn nếu phiên bản của bạn khác biệt.
Xây dựng venv cho từng ứng dụng
Ubuntu tách module venv ra khỏi gói Python cơ bản, vì vậy trên một image tối giản, lần thử đầu tiên sẽ thất bại kèm thông báo nêu rõ những gì còn thiếu.
The virtual environment was not created successfully because ensurepip is not
available. On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.
apt install python3.12-venvHãy cài đặt nó, sau đó tạo môi trường với tư cách người dùng sẽ sở hữu mã nguồn.
sudo apt update
sudo apt install -y python3-venv
sudo install -d -o deploy -g deploy -m 755 /srv/myapp
sudo -u deploy python3 -m venv /srv/myapp/.venv
sudo -u deploy /srv/myapp/.venv/bin/pip install -r /srv/myapp/requirements.txtLưu ý những gì không có ở đó: không có source, không có activate. /srv/myapp/.venv/bin/pip cài đặt vào môi trường đó dựa trên vị trí của file binary, không phải do bất kỳ thứ gì bạn đã export vào shell. Hãy xác nhận điều này trước khi tiếp tục.
/srv/myapp/.venv/bin/python -c 'import sys; print(sys.prefix)'Lệnh đó sẽ in ra /srv/myapp/.venv. Nếu nó in ra /usr, bạn đang chạy trình thông dịch của hệ thống và các gói của bạn đã được cài vào nơi không mong muốn.
Hai thuộc tính của venv quyết định những gì bạn có thể làm với nó sau này. Một venv không thể di chuyển (relocatable), vì mọi script trong bin/ đều chứa một dòng shebang tuyệt đối: head -1 /srv/myapp/.venv/bin/pip đọc #!/srv/myapp/.venv/bin/python. Đổi tên thư mục cha và các script đó sẽ thất bại với lỗi bad interpreter: No such file or directory. Một venv cũng ghim trình thông dịch đã tạo ra nó, được ghi lại dưới dạng dòng home trong /srv/myapp/.venv/pyvenv.cfg, và bin/python3 là một symlink trỏ đến file binary đó. Nếu bạn nâng cấp bản phát hành khiến python3.12 bị xóa, symlink sẽ không có đích đến và service sẽ chết ngay khi khởi động với lỗi No such file or directory. Cả hai trường hợp đều có cùng một cách sửa: xóa venv và xây dựng một cái mới từ requirements.txt. Việc xây dựng lại chỉ mất vài giây. Đừng bao giờ copy venv giữa các máy.
Vị trí của venv và quyền sở hữu
Hãy đặt venv bên cạnh mã nguồn tại /srv/myapp/.venv và duy trì mỗi ứng dụng một venv riêng. Khi đó, việc triển khai chỉ gói gọn trong một thư mục, unit của systemd sẽ có đường dẫn cố định không thay đổi, và hai ứng dụng sẽ không bao giờ làm hỏng lẫn nhau do nâng cấp dependency dùng chung. Đừng đặt venv tại bất kỳ vị trí nào mà web server của bạn công khai file trực tiếp, vì nó chứa các dependency và thường là cả cấu hình của bạn.
Quyền sở hữu cần được lưu ý kỹ. Hãy để một user deploy sở hữu mã nguồn và môi trường, sau đó chỉ cấp quyền đọc và thực thi cho service account.
sudo adduser --system --group --no-create-home myapp
sudo chown -R deploy:myapp /srv/myapp
sudo chmod -R o-rwx /srv/myappService giờ đây có thể import các dependency nhưng không thể ghi đè lên chúng. Điều này có nghĩa là một lỗi thực thi mã trong ứng dụng web không thể âm thầm thay thế thư viện trên ổ đĩa và tồn tại sau khi khởi động lại. Lập luận tương tự áp dụng cho phần còn lại của máy chủ đã được đề cập trong chạy các service với quyền hạn tối thiểu.
pipx cho các công cụ dòng lệnh
pipx cài đặt các ứng dụng, không phải thư viện. Mỗi công cụ có một môi trường riêng nằm trong ~/.local/share/pipx/venvs/<name>, và các file thực thi của công cụ đó được liên kết vào ~/.local/bin, vì vậy hai công cụ cần các phiên bản khác nhau của cùng một thư viện sẽ không bao giờ xung đột.
sudo apt update
sudo apt install -y pipx
pipx ensurepath
pipx install httpiepipx ensurepath thêm ~/.local/bin vào PATH bằng cách chỉnh sửa file khởi động shell của bạn. Nó không thể thay đổi shell bạn đang sử dụng, vì vậy http: command not found ngay sau khi cài đặt thường có nghĩa là bạn chưa đăng xuất và đăng nhập lại. ~/.profile mặc định của Ubuntu chỉ thêm ~/.local/bin khi thư mục đó đã tồn tại lúc đăng nhập, đó là lý do tại sao lỗi này chỉ xảy ra một lần trên tài khoản mới và không bao giờ lặp lại.
Nếu bạn trỏ pipx vào một thư viện, nó sẽ từ chối với thông báo bắt đầu bằng:
No apps associated with package requests or its dependencies.Đó là cách công cụ báo cho bạn biết bạn đang dùng sai mục đích. Các thư viện thuộc về venv của một ứng dụng.
Chi tiết quan trọng trên máy chủ là vị trí. Một lệnh pipx install thông thường đặt mọi thứ vào thư mục home của một người dùng. Một unit systemd chạy dưới quyền myapp không thể thấy nó, một cron job của root không thể thấy nó, và sudo cũng sẽ không tìm thấy nó, vì secure_path trong /etc/sudoers thay thế PATH bằng một danh sách cố định. Đối với một công cụ mà toàn bộ máy cần dùng, hãy cài đặt nó ở phạm vi toàn hệ thống.
sudo pipx install --global ansible
sudo pipx ensurepath --globalFlag --global đặt các môi trường vào /opt/pipx và liên kết các file thực thi vào /usr/local/bin, vốn nằm trong PATH mặc định và bên trong secure_path. Hãy kiểm tra phiên bản của bạn trước với pipx --version, vì Ubuntu 24.04 đóng gói pipx 1.4.3, cũ hơn --global, và pipx cũ hơn sẽ phản hồi bằng unrecognized arguments: --global. Trên phiên bản đó, hãy tự thiết lập hai thư mục được ghi nhận:
sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install ansible
command -v ansiblecommand -v ansible sẽ in ra /usr/local/bin/ansible. Nếu nó in ra một đường dẫn nằm trong /home, công cụ đã được cài vào tài khoản của một người dùng đơn lẻ và không có service nào tìm thấy nó.
uv khi bạn cần một lockfile
uv là một binary duy nhất từ Astral, thay thế cho các chức năng của pip, venv và pip-tools, đồng thời có khả năng tải xuống các trình thông dịch Python. Nó đủ nhanh để bạn thấy rõ sự khác biệt trên một VPS cấu hình thấp và nó tạo ra một lockfile thực thụ.
Trình cài đặt chính thức đặt uv và uvx vào ~/.local/bin:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --versionViệc pipe một script trực tiếp vào shell trên server cần được cân nhắc kỹ. Hãy ghim phiên bản trong URL và đọc nội dung file trước khi chạy:
curl -LsSf https://astral.sh/uv/0.12.3/install.sh -o uv-install.sh
less uv-install.sh
sh uv-install.shpipx install uv cũng hoạt động nếu bạn đã cài sẵn pipx. uv là một binary độc lập, không phụ thuộc vào Python, nên việc copy nó vào /usr/local/bin là cách hợp lệ để chia sẻ cho mọi user trên hệ thống.
Với một dự án có pyproject.toml, quy trình làm việc gồm bốn lệnh, và chỉ lệnh cuối cùng là chạy trên server.
uv init myapp
uv add flask gunicorn
uv lock
uv sync --frozen --no-devuv lock sẽ tạo ra uv.lock, một lockfile đa nền tảng chứa các phiên bản đã được resolve chính xác, bạn cần commit file này cùng với mã nguồn. uv sync sẽ xây dựng .venv tại thư mục gốc của dự án để khớp với lockfile. Trên server, --frozen là flag quan trọng: tài liệu định nghĩa nó là việc sử dụng các phiên bản trong lockfile làm nguồn tin cậy duy nhất thay vì kiểm tra xem lockfile có lỗi thời hay không, đây chính là hành vi cần thiết khi deploy. --no-dev sẽ loại bỏ các dependency thuộc nhóm development.
Một dự án requirements.txt hiện có không cần chuyển đổi, vì uv hiểu ngôn ngữ của pip:
uv venv /srv/myapp/.venv
uv pip install --python /srv/myapp/.venv/bin/python -r /srv/myapp/requirements.txtKết quả thu được là một virtual environment thông thường. .venv/bin/python hoạt động chính xác như khi python3 -m venv tạo ra nó, vì vậy không có gì thay đổi trong các phần sau của hướng dẫn này.
Có một mặc định của uv bạn cần biết trước khi dùng trên server. Thiết lập python-preference mặc định là managed, được tài liệu hóa là ưu tiên "những phiên bản được tải xuống và cài đặt bởi uv" thay vì trình thông dịch đã có sẵn trên hệ thống. Vì vậy, uv venv --python 3.13 trên một máy chỉ có sẵn bản 3.12 sẽ âm thầm tải 3.13 về ~/.local/share/uv/python thay vì báo lỗi. Điều này tiện lợi trên laptop nhưng gây bất ngờ trên server, vì service của bạn giờ đây phụ thuộc vào một trình thông dịch nằm trong thư mục home mà apt upgrade sẽ không bao giờ cập nhật (patch). Hãy đặt python-preference thành only-system trong uv.toml nếu bạn muốn dùng trình thông dịch của hệ điều hành. Nếu bạn muốn đặt môi trường ở một nơi khác ngoài thư mục gốc của dự án, UV_PROJECT_ENVIRONMENT sẽ chỉ định thư mục cần dùng cho virtual environment của dự án đó.
Trỏ systemd vào trình thông dịch venv, không phải vào activate
Đây là nơi hầu hết các triển khai Python gặp lỗi, và nguyên nhân là do hiểu lầm về chức năng của activate.
bin/activate là một shell script. Nó thêm thư mục bin của venv vào đầu PATH, thiết lập VIRTUAL_ENV, lưu lại các giá trị cũ để deactivate có thể khôi phục chúng, và thay đổi prompt của bạn. Nó không chứa bất kỳ thứ gì mà chính trình thông dịch đọc. Việc kích hoạt (activation) chỉ là sự tiện lợi cho con người khi gõ python tại dòng lệnh.
Thứ thực sự chọn môi trường là file trình thông dịch mà bạn thực thi. Khi /srv/myapp/.venv/bin/python khởi động, module site của Python sẽ tìm file pyvenv.cfg trong thư mục chứa file thực thi và thư mục ngay phía trên nó. Việc tìm thấy /srv/myapp/.venv/pyvenv.cfg sẽ thiết lập sys.prefix thành venv, từ đó đưa site-packages của venv đó vào sys.path. Đó là toàn bộ cơ chế. Nó không cần biến môi trường và không cần shell.
Vì vậy, unit này không bao giờ khởi động:
[Service]
ExecStart=source /srv/myapp/.venv/bin/activate && gunicorn app:appmyapp.service: Failed to locate executable source: No such file or directory
myapp.service: Failed at step EXEC spawning source: No such file or directory
myapp.service: Main process exited, code=exited, status=203/EXECExecStart không phải là một dòng lệnh shell. systemd thực thi trực tiếp một chương trình, vì vậy không có lệnh nội bộ source nào cả, && được truyền vào như một đối số nguyên văn, và không có gì được mở rộng.
Và unit này khởi động, sau đó chết:
[Service]
ExecStart=/usr/bin/python3 /srv/myapp/app.pyModuleNotFoundError: No module named 'flask'/usr/bin/python3 là trình thông dịch hệ thống, và sys.path của nó chưa bao giờ chứa venv của bạn. Cùng lệnh đó hoạt động trong phiên SSH của bạn chỉ vì bạn đã kích hoạt venv ở đó, nên shell đã phân giải python3 thông qua PATH thành .venv/bin/python3.
Việc bao bọc lệnh trong /bin/bash -c 'source ... && gunicorn ...' có hoạt động. Nó cũng đặt một shell vào giữa systemd và tiến trình của bạn mà không mang lại lợi ích gì, trong khi một đường dẫn tuyệt đối sẽ giải quyết vấn đề:
[Unit]
Description=myapp web service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
Environment=PYTHONUNBUFFERED=1
Environment=PATH=/srv/myapp/.venv/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/srv/myapp/.venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
journalctl -u myapp -n 50 --no-pagersystemctl status myapp sẽ báo active (running) với Main PID chính là tiến trình gunicorn của bạn. Nếu là kết quả khác, hãy đọc journal.
Dòng Environment=PATH= không dành cho ExecStart, vì nó đã mang đường dẫn đầy đủ. Nó dành cho các tiến trình mà ứng dụng của bạn khởi chạy. Một service kế thừa PATH mặc định ngắn từ systemd, vì vậy mã Python gọi subprocess.run(["ffmpeg", ...]), hoặc một lệnh quản lý gọi ra script console từ venv, sẽ không tìm thấy những gì nó cần. Việc đặt thư mục bin của venv lên đầu là phần duy nhất của activate mà một service thực sự sử dụng. Kiểm tra xem unit thực sự nhận được gì với systemctl show -p Environment myapp.
Quy tắc tương tự áp dụng cho công việc định kỳ. cron chạy các job với PATH là /usr/bin:/bin, vì vậy một dòng crontab đọc python3 /srv/myapp/cleanup.py sẽ chạy trình thông dịch hệ thống và thất bại với ModuleNotFoundError vào lúc ba giờ sáng, và lỗi sẽ được gửi đến một local mail spool mà không ai đọc. Hãy viết đường dẫn venv tuyệt đối ở đó. Để nhận kết quả đó trong journal và có bản ghi về lần chạy cuối cùng, một cặp systemd service và timer sẽ sử dụng cùng dòng ExecStart đó.
Docker có thay thế quyết định này không?
Container có hệ thống file riêng, vì vậy câu hỏi này chỉ thay đổi hình thức chứ không biến mất. Trong một image chính thức như python:3.12-slim, Python được build sẵn vào /usr/local và không mang theo marker EXTERNALLY-MANAGED nào, nên pip install với quyền root là cách chuẩn để cài thêm các gói, và venv lúc này không mang lại nhiều giá trị. Nếu bạn build FROM ubuntu:24.04, bạn sẽ gặp lại externally-managed-environment bên trong image, với cùng lý do như trên host: đó là trình thông dịch của bản phân phối (distribution) và nó mang theo file marker của bản phân phối đó.
Nhiều image vẫn sử dụng venv vì nó giúp cho quá trình multi-stage build trở nên đơn giản. Giai đoạn builder cài đặt vào /opt/venv, và giai đoạn runtime chỉ cần copy thư mục đó và bỏ lại các trình biên dịch. Vấn đề với lệnh activate vẫn đi kèm theo đó. Một dòng RUN source /opt/venv/bin/activate chỉ ảnh hưởng đến shell của layer build đó, vì vậy khi chạy runtime, container sẽ khởi động bằng trình thông dịch của hệ thống và gây ra lỗi ModuleNotFoundError. Hãy thiết lập ENV PATH="/opt/venv/bin:$PATH", hoặc cung cấp cho CMD đường dẫn tuyệt đối /opt/venv/bin/gunicorn. Đây chính là lỗi tương tự như trên systemd, chỉ khác là nằm ở một file khác.
Vì vậy, container thay thế câu hỏi về trình thông dịch, vì image đã ghim (pin) trình thông dịch và mọi thứ bên dưới nó. Nó không thay thế câu hỏi về việc ghim phiên bản. Một image được build từ file requirements.txt không được ghim sẽ phân giải ra các phiên bản khác nhau vào tháng sau, nghĩa là tag của image thì có thể tái lập (reproducible) nhưng quá trình build tạo ra nó thì không. Một file lock như uv.lock, hoặc một file requirements được ghim đầy đủ, mới là thứ lấp đầy khoảng trống đó, dù có dùng container hay không. Và khi một ứng dụng chạy trên một VPS dưới sự quản lý của systemd, container chủ yếu chuyển quyết định này vào một file Dockerfile, vì systemd vốn đã tự khởi động lại tiến trình bị lỗi và ghi lại output của nó vào journal. Chạy Docker trên VPS sẽ thực sự hữu ích khi bạn muốn chính image đã build là thứ bạn triển khai.
FAQ
Tôi có thể dùng pip install với --break-system-packages không?
Không nên dùng trên máy chủ mà bạn cần duy trì hoạt động ổn định. Flag này thực hiện đúng như tên gọi của nó: nó loại bỏ lớp bảo vệ, và pip sẽ ghi đè vào /usr/local/lib/python3.12/dist-packages, vốn có độ ưu tiên cao hơn thư mục của apt trên sys.path. Phiên bản của bạn sau đó sẽ ghi đè lên phiên bản của hệ thống cho mọi script đang chạy dưới quyền /usr/bin/python3, trong khi apt vẫn tin rằng phiên bản của nó đang được cài đặt, vì vậy sẽ không có gì phát hiện ra xung đột cho đến khi có lỗi xảy ra. Bên trong một container image mà bạn build lại từ đầu mỗi lần, thiệt hại chỉ dừng lại ở image đó, nên có thể chấp nhận được. Trên máy chủ mà bạn quản lý, hãy tạo một venv. Chỉ cần một lệnh duy nhất.
Virtual environment nên đặt ở đâu trên máy chủ?
Bên trong thư mục của ứng dụng, đặt tên là /srv/myapp/.venv, thuộc sở hữu của một deploy user, với service account chỉ có quyền đọc và thực thi. Hãy giữ một venv riêng cho mỗi ứng dụng, vì dùng chung venv có nghĩa là nâng cấp ứng dụng này có thể làm hỏng ứng dụng kia. Đừng di chuyển hoặc sao chép venv sau khi đã tạo: mọi script trong thư mục bin/ của nó đều có đường dẫn tuyệt đối được ghi cứng trong dòng shebang, nên nếu di chuyển venv, nó sẽ bị lỗi bad interpreter: No such file or directory. Hãy xóa nó đi và build lại từ requirements.txt.
Tại sao systemd service của tôi bị lỗi ModuleNotFoundError?
Vì unit đang thực thi một trình thông dịch không phải của venv. Hãy chạy systemctl cat myapp và đọc ExecStart. Nó phải chỉ định /srv/myapp/.venv/bin/python, hoặc một console script từ cùng thư mục bin/ đó, bằng đường dẫn tuyệt đối. Việc source activate trong file unit không có tác dụng, vì ExecStart không phải là shell, và systemd sẽ báo lỗi Failed to locate executable source với status=203/EXEC. Hãy thêm Environment=PATH=/srv/myapp/.venv/bin:/usr/local/bin:/usr/bin:/bin để bất kỳ subprocess nào mà code của bạn khởi chạy cũng tìm thấy các công cụ của venv.
Tôi có nên dùng uv thay cho venv và pip không?
Hãy dùng uv khi bạn cần một lockfile, khi thời gian cài đặt quá lâu gây phiền toái, hoặc khi bạn cần một phiên bản Python mà hệ điều hành không cung cấp. Nó tạo ra một venv thông thường, vì vậy unit systemd và cấu trúc file không thay đổi, và uv sync --frozen sẽ cài đặt chính xác những gì lockfile ghi lại. Nếu một ứng dụng duy nhất được deploy từ git với file requirements.txt đã được ghim phiên bản và quá trình cài đặt chỉ mất vài giây, thì python3 -m venv đã là quá đủ, và bạn cũng bớt được một binary phải cập nhật trên máy chủ.