SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor

Python venv, pipx, atau uv di Server Ubuntu

Ubuntu 24.04 menolak pip install dengan error externally-managed-environment. Pilih venv, pipx, atau uv sesuai jenis instalasi, lalu arahkan systemd ke environment tersebut.

Mengapa pip install gagal pada server Ubuntu baru

Memilih antara Python venv, pipx, dan uv di server bergantung pada satu pertanyaan: apa yang ingin Anda instal? Dependensi aplikasi harus ditempatkan dalam virtual environment di dalam direktori aplikasi itu sendiri. Tool command-line yang ingin Anda jalankan berdasarkan namanya harus ditempatkan di pipx. uv dapat melakukan kedua tugas tersebut dan menambahkan lockfile. Lockfile mulai penting saat mesin kedua harus membuat environment yang sama. Tidak satu pun dari tool tersebut menginstal ke system Python, karena server Ubuntu saat ini menolak tindakan tersebut.

Jalankan sudo pip install requests pada Ubuntu 24.04, dan pip berhenti sebelum mengunduh satu file pun.

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.

Ini adalah PEP 668 (Python enhancement proposal 668, "externally managed environments") yang berfungsi sebagaimana mestinya. Debian dan Ubuntu menempatkan file penanda di samping interpreter pada /usr/lib/python3.12/EXTERNALLY-MANAGED, dan pip menolak menulis ke interpreter apa pun yang memiliki file tersebut.

Aturan ini ada karena urutan sys.path. apt menginstal library ke /usr/lib/python3/dist-packages. Jika dijalankan sebagai root terhadap system interpreter, pip menulis ke /usr/local/lib/python3.12/dist-packages, dan sistem packaging Debian menempatkan direktori tersebut lebih awal dalam search path. Tampilkan sendiri urutannya dengan python3 -c 'import sys; print(sys.path)'. Dengan demikian, salinan yang ditulis pip akan menutupi salinan yang diinstal apt untuk setiap program pada server yang berjalan menggunakan /usr/bin/python3, termasuk tool milik distribusi itu sendiri. cloud-init mengimpor requests, jinja2, dan PyYAML dari interpreter tersebut. Jika salah satunya di-upgrade dengan pip lalu menghasilkan release yang tidak kompatibel, sesuatu yang tidak pernah Anda ubah dapat gagal saat boot berikutnya dengan traceback yang menyebut package yang tidak Anda ketahui berada dalam rantai dependensi. apt tetap mencatat versinya sendiri sebagai terinstal, sehingga tidak ada peringatan. Perbaikannya adalah menghapus salinan milik pip dengan sudo pip uninstall --break-system-packages requests, karena sudo apt reinstall python3-requests saja hanya menulis ulang salinan apt yang sejak awal sudah tertutupi.

Aturan berikutnya sederhana. System Python adalah milik distribusi. Jangan menginstal apa pun ke dalamnya, jangan meng-upgrade library-nya dengan pip, dan jangan menghapus file EXTERNALLY-MANAGED hanya untuk menghilangkan pesan tersebut. Satu-satunya tugas yang perlu diberikan kepada /usr/bin/python3 adalah membuat virtual environment.

venv vs pipx vs uv: aturan pemilihan

Pilih berdasarkan apa yang akan Anda instal, bukan berdasarkan alat yang paling baru Anda baca.

  • Aplikasi yang Anda deploy dan jalankan sebagai service, seperti project Django atau Flask: satu virtual environment (venv) di dalam direktori aplikasi tersebut.
  • Tool command-line yang ingin Anda gunakan pada PATH, seperti ansible atau httpie: pipx, yang memberikan setiap tool private environment dan satu link pada PATH.
  • Project yang memerlukan lockfile, instalasi lebih cepat, atau versi Python yang tidak disediakan oleh distribusi: uv, yang menghasilkan venv biasa serta file uv.lock.
  • Library yang diperlukan oleh tool distribusi, bukan oleh kode Anda: sudo apt install python3-<name>, satu-satunya cara yang didukung untuk menambahkan apa pun ke system interpreter.

pipx dan uv tool install melakukan pekerjaan yang sama, sehingga server yang sudah memiliki uv tidak memerlukan pipx juga. Framework web yang Anda pilih tidak mengubah apa pun di sini: Django dan Flask pada VPS berbeda dalam hal yang ditempatkan di requirements.txt, bukan dalam cara environment di sekitarnya dibangun. Semua langkah di bawah menggunakan Ubuntu 24.04 dan Python 3.12-nya, jadi sesuaikan versi di dalam path jika versi Anda berbeda.

Membuat venv untuk setiap aplikasi

Ubuntu memisahkan modul venv dari paket Python dasar. Pada image minimal, upaya pertama gagal dengan pesan yang menyebutkan secara tepat komponen yang hilang.

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

Instal komponen tersebut, lalu buat environment sebagai pengguna yang akan memiliki kode tersebut.

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

Perhatikan hal yang tidak ada: tidak ada source dan tidak ada activate. /srv/myapp/.venv/bin/pip diinstal ke dalam environment tersebut berdasarkan lokasi binary, bukan berdasarkan variabel apa pun yang diekspor ke shell. Konfirmasikan hal ini sebelum melanjutkan.

/srv/myapp/.venv/bin/python -c 'import sys; print(sys.prefix)'

Perintah tersebut menampilkan /srv/myapp/.venv. Jika yang ditampilkan adalah /usr, Anda sedang menjalankan system interpreter dan paket Anda diinstal ke lokasi yang tidak dimaksudkan.

Dua properti venv menentukan tindakan yang dapat Anda lakukan setelahnya. Venv tidak dapat dipindahkan, karena setiap skrip entry point dalam bin/ memuat baris shebang absolut: head -1 /srv/myapp/.venv/bin/pip membaca #!/srv/myapp/.venv/bin/python. Jika direktori induk diganti namanya, skrip tersebut gagal dengan bad interpreter: No such file or directory. Venv juga mengikat interpreter yang membuatnya. Informasi ini dicatat sebagai baris home dalam /srv/myapp/.venv/pyvenv.cfg, dan bin/python3 merupakan symlink ke /usr/bin/python3. Jika Anda meng-upgrade release sehingga path tersebut sekarang mengarah ke Python yang lebih baru, interpreter baru akan mencari paket dalam direktori lib/python3.13 yang tidak dimiliki venv. Akibatnya, service gagal saat start dengan ModuleNotFoundError. Kedua kasus tersebut memiliki solusi yang sama: hapus venv dan buat yang baru dari requirements.txt. Pembuatan ulang hanya memerlukan waktu beberapa detik. Jangan pernah menyalin venv antar-mesin.

Lokasi venv dan pemiliknya

Letakkan venv di samping kode pada /srv/myapp/.venv, dan gunakan satu venv untuk setiap aplikasi. Dengan demikian, deployment berada dalam satu direktori, unit systemd memiliki path yang tidak berubah, dan dua aplikasi tidak dapat saling mengganggu melalui satu pembaruan dependensi bersama. Jangan letakkan venv di lokasi yang menerbitkan file secara langsung melalui web server, karena venv berisi dependensi dan sering kali konfigurasi Anda.

Kepemilikan perlu diperhatikan. Jadikan pengguna deploy sebagai pemilik kode dan environment, lalu berikan akun service akses baca dan eksekusi saja.

sudo adduser --system --group --no-create-home myapp
sudo chown -R deploy:myapp /srv/myapp
sudo chmod -R o-rwx /srv/myapp

Service kini dapat mengimpor dependensinya, tetapi tidak dapat menimpanya. Dengan demikian, bug eksekusi kode pada aplikasi web tidak dapat diam-diam mengganti library di disk dan tetap bertahan setelah restart. Prinsip yang sama untuk bagian lain dari mesin dibahas dalam menjalankan service dengan hak akses minimum.

pipx untuk alat baris perintah

pipx menginstal aplikasi, bukan library. Setiap alat mendapatkan environment-nya sendiri di bawah ~/.local/share/pipx/venvs/<name>, dan executable alat tersebut ditautkan ke ~/.local/bin. Dengan demikian, dua alat yang memerlukan versi berbeda dari library yang sama tidak akan saling bertabrakan.

sudo apt update
sudo apt install -y pipx
pipx ensurepath
pipx install httpie

pipx ensurepath menambahkan ~/.local/bin ke PATH dengan mengedit file startup shell Anda. Perintah ini tidak dapat mengubah shell yang sedang Anda gunakan. Jadi, jika http: command not found langsung setelah instalasi biasanya berarti Anda belum logout lalu login kembali. ~/.profile default Ubuntu hanya menambahkan ~/.local/bin jika direktori tersebut sudah ada saat login. Karena itu, masalah ini muncul sekali pada akun baru dan tidak muncul lagi.

Jika Anda mengarahkan pipx ke sebuah library, pipx akan menolaknya dengan pesan yang dimulai dengan:

No apps associated with package requests.

Pesan tersebut menunjukkan bahwa pipx bukan alat yang tepat untuk tugas itu. Library harus ditempatkan dalam venv aplikasi.

Hal yang penting pada server adalah lokasi instalasi. pipx install biasa menempatkan semuanya di direktori home satu pengguna. Unit systemd yang berjalan sebagai myapp tidak dapat mengaksesnya. Cron job yang berjalan sebagai root juga tidak dapat mengaksesnya. sudo juga tidak akan menemukannya karena secure_path di /etc/sudoers menggantikan PATH dengan daftar tetap. Jika sebuah alat harus tersedia di seluruh mesin, instal alat tersebut secara global.

sudo pipx install --global ansible
sudo pipx ensurepath --global

Flag --global menempatkan environment di /opt/pipx dan menautkan executable ke /usr/local/bin. Direktori tersebut berada di PATH default dan di dalam secure_path. Periksa versi Anda terlebih dahulu dengan pipx --version. Ubuntu 24.04 menyediakan pipx 1.4.3, yang lebih lama daripada --global, dan pipx versi lama akan menampilkan unrecognized arguments: --global. Pada versi tersebut, tetapkan sendiri kedua direktori yang didokumentasikan:

sudo env PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install ansible
command -v ansible

command -v ansible harus menampilkan /usr/local/bin/ansible. Jika yang ditampilkan adalah path di bawah /home, alat tersebut terinstal di akun satu pengguna sehingga tidak ada service yang dapat menemukannya.

uv saat Anda memerlukan lockfile

uv adalah satu binary dari Astral yang mencakup fungsi pip, venv, dan pip-tools. uv juga dapat mengunduh interpreter.

uv cukup cepat sehingga perbedaannya terlihat pada VPS kecil. uv juga menulis lockfile yang sebenarnya.

Installer resmi menempatkan uv dan uvx di ~/.local/bin:

curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version

Menyalurkan skrip ke shell pada server memerlukan kehati-hatian. Tetapkan versi pada URL dan baca file tersebut sebelum menjalankannya:

curl -LsSf https://astral.sh/uv/0.12.3/install.sh -o uv-install.sh
less uv-install.sh
sh uv-install.sh

pipx install uv juga dapat digunakan jika pipx sudah tersedia. uv adalah binary mandiri yang tidak memiliki dependensi Python sendiri. Karena itu, menyalinnya ke /usr/local/bin merupakan cara yang valid untuk membagikannya kepada semua pengguna pada server.

Untuk project dengan pyproject.toml, alurnya terdiri atas empat perintah. Hanya perintah terakhir yang dijalankan pada server.

uv init myapp
uv add flask gunicorn
uv lock
uv sync --frozen --no-dev

uv lock menulis uv.lock, yaitu lockfile lintas platform yang menyimpan versi hasil resolusi secara persis. Anda melakukan commit terhadap file tersebut bersama kode Anda. uv sync membangun .venv di root project agar sesuai dengan lockfile. Pada server, --frozen adalah flag yang penting. Dokumentasi mendefinisikannya sebagai penggunaan versi dalam lockfile sebagai sumber kebenaran, bukan pemeriksaan apakah lockfile sudah mutakhir. Inilah perilaku yang diperlukan saat deployment. --no-dev tidak menyertakan dependency group untuk development.

Project requirements.txt yang sudah ada tidak memerlukan konversi karena uv menggunakan bahasa pip:

uv venv /srv/myapp/.venv
uv pip install --python /srv/myapp/.venv/bin/python -r /srv/myapp/requirements.txt

Hasilnya adalah virtual environment biasa dengan satu perbedaan. uv tidak menginstal pip ke dalamnya. Karena itu, .venv/bin/pip dan .venv/bin/python -m pip tidak berfungsi kecuali Anda membuat environment dengan uv venv --seed. Gunakan uv pip untuk melakukan instalasi. Tidak ada bagian lain dalam panduan ini yang menjalankan pip di dalam environment tersebut. Jadi, bagian lain tidak mengubah perilaku ini.

Salah satu default uv perlu diketahui sebelum digunakan pada server. Setting python-preference secara default bernilai managed. Dokumentasi menjelaskan bahwa setting ini memilih "interpreter yang diunduh dan diinstal oleh uv" daripada interpreter yang sudah tersedia pada sistem. Jadi, uv venv --python 3.13 pada server yang hanya menyediakan 3.12 akan secara diam-diam mengunduh 3.13 ke ~/.local/share/uv/python, bukan gagal. Hal ini praktis pada laptop, tetapi dapat mengejutkan pada server karena service Anda kini bergantung pada interpreter yang berada di direktori home dan tidak akan pernah diperbarui oleh apt upgrade. Tetapkan python-preference ke only-system di uv.toml jika Anda ingin menggunakan interpreter dari distribusi. Jika Anda ingin menempatkan environment di lokasi selain root project, UV_PROJECT_ENVIRONMENT menentukan direktori yang digunakan untuk virtual environment project.

Arahkan systemd ke interpreter venv, bukan ke activate

Di sinilah sebagian besar deployment Python gagal. Penyebabnya adalah kesalahpahaman tentang fungsi activate.

bin/activate adalah skrip shell. Skrip ini menambahkan direktori bin milik venv ke bagian awal PATH, menetapkan VIRTUAL_ENV, menyimpan nilai lama agar deactivate dapat memulihkannya, dan mengubah prompt. Skrip ini tidak berisi apa pun yang dibaca oleh interpreter. Aktivasi hanya memudahkan pengguna yang mengetik python pada prompt.

Yang benar-benar memilih environment adalah file interpreter yang Anda jalankan. Saat /srv/myapp/.venv/bin/python dimulai, modul Python site mencari file pyvenv.cfg di direktori yang berisi executable tersebut dan satu tingkat di atasnya. Jika /srv/myapp/.venv/pyvenv.cfg ditemukan, sys.prefix ditetapkan ke venv. Dengan demikian, direktori site-packages milik venv ditempatkan pada sys.path. Itulah seluruh mekanismenya. Mekanisme ini tidak memerlukan environment variable atau shell.

Karena itu, unit ini tidak pernah berjalan:

[Service]
ExecStart=source /srv/myapp/.venv/bin/activate && gunicorn app:app
myapp.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/EXEC

ExecStart bukan baris perintah shell. systemd menjalankan program secara langsung. Jadi, tidak ada builtin source, && diteruskan sebagai argumen literal, dan ekspansi shell tidak dijalankan selain beberapa ekspansi yang didokumentasikan oleh systemd.

Unit ini berjalan, lalu berhenti:

[Service]
ExecStart=/usr/bin/python3 /srv/myapp/app.py
ModuleNotFoundError: No module named 'flask'

/usr/bin/python3 adalah interpreter sistem. Nilai sys.path miliknya tidak pernah berisi venv Anda. Perintah yang sama berhasil dalam sesi SSH hanya karena Anda telah mengaktifkan venv di sana. Akibatnya, shell menyelesaikan python3 melalui PATH ke .venv/bin/python3.

Membungkus perintah dalam /bin/bash -c 'source ... && gunicorn ...' memang berhasil. Namun, cara ini menambahkan shell di antara systemd dan proses Anda tanpa manfaat. Satu path absolut sudah cukup:

[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.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
journalctl -u myapp -n 50 --no-pager

systemctl status myapp seharusnya menampilkan active (running) dengan Main PID yang merupakan proses gunicorn Anda. Jika hasilnya berbeda, baca journal.

Baris Environment=PATH= bukan ditujukan untuk ExecStart, karena perintah tersebut sudah menggunakan path lengkap. Baris itu diperlukan oleh proses yang dimulai oleh aplikasi Anda. Service mewarisi PATH default yang pendek dari systemd. Karena itu, kode Python yang memanggil subprocess.run(["ffmpeg", ...]), atau perintah manajemen yang menjalankan console script dari venv, tidak akan menemukan yang diperlukan. Menempatkan direktori bin milik venv di urutan pertama adalah satu-satunya bagian dari activate yang benar-benar digunakan oleh service. Periksa nilai yang sebenarnya diterima unit dengan systemctl show -p Environment myapp.

Aturan yang sama berlaku untuk pekerjaan terjadwal. Di Ubuntu, cron tidak meneruskan PATH dari shell atau daemon ke suatu job. Jika crontab tidak menetapkan PATH sendiri, setiap job menerima /usr/bin:/bin default yang dikompilasi ke dalam cron. /etc/environment tidak mengubahnya. Nilai tersebut tidak pernah berisi venv Anda. Akibatnya, baris crontab yang berisi python3 /srv/myapp/cleanup.py menjalankan interpreter sistem dan gagal dengan ModuleNotFoundError pada pukul tiga pagi. cron mengirimkan error ke pemilik crontab melalui email, atau ke alamat MAILTO jika Anda menetapkannya. Pada sebagian besar server, alamat tersebut berarti mailbox lokal yang tidak dibaca siapa pun. Jika tidak ada program email yang terpasang, cron membuang output tersebut dan hanya menulis No MTA installed, discarding output ke system log. Gunakan path absolut venv di sana juga. Untuk mengirim output tersebut ke journal dan menyimpan catatan eksekusi terakhir, gunakan pasangan service dan timer systemd dengan baris ExecStart yang sama.

Apakah Docker menggantikan keputusan ini?

Container memiliki filesystem sendiri, sehingga pertanyaannya berubah bentuk, bukan hilang. Dalam image resmi seperti python:3.12-slim, Python sudah terpasang di /usr/local dan tidak memiliki marker EXTERNALLY-MANAGED. Karena itu, pip install sebagai root adalah cara yang memang ditujukan untuk menambahkan package, sedangkan venv hanya memberikan sedikit manfaat. Sebaliknya, build FROM ubuntu:24.04 dan Anda akan kembali menemukan externally-managed-environment di dalam image, karena alasannya sama seperti pada host: interpreter tersebut berasal dari distribusi dan membawa file marker milik distribusi.

Banyak image tetap menggunakan venv karena pendekatan ini menyederhanakan build multi-stage. Stage builder memasang package ke /opt/venv, lalu stage runtime menyalin satu direktori tersebut dan tidak menyertakan compiler. Masalah aktivasi ikut terbawa. Baris RUN source /opt/venv/bin/activate hanya memengaruhi shell pada layer build tersebut. Saat runtime, container dimulai dengan system interpreter dan menghasilkan ModuleNotFoundError. Tetapkan ENV PATH="/opt/venv/bin:$PATH", atau berikan CMD path absolut /opt/venv/bin/gunicorn. Ini adalah bug yang sama seperti pada systemd, tetapi berada di file yang berbeda.

Jadi, container menggantikan pertanyaan tentang interpreter karena image menetapkan interpreter dan seluruh isinya. Namun, container tidak menggantikan pertanyaan tentang pinning. Image yang dibangun dari requirements.txt tanpa pinning akan memilih versi yang berbeda bulan depan. Artinya, tag image dapat direproduksi, tetapi build yang menghasilkannya tidak. Lockfile seperti uv.lock, atau file requirements yang seluruh versinya dipin, diperlukan untuk menutup celah tersebut, baik menggunakan container maupun tidak. Jika satu aplikasi berjalan pada satu VPS dengan systemd, container pada dasarnya hanya memindahkan keputusan yang sama ke dalam Dockerfile, karena systemd sudah me-restart proses yang gagal dan merekam output-nya di journal. Menjalankan Docker pada VPS bermanfaat ketika Anda ingin menjadikan image yang sudah dibangun sebagai artefak yang di-deploy.

FAQ

Apakah saya dapat langsung menggunakan pip install dengan --break-system-packages?

Tidak pada server yang harus tetap berjalan. Flag tersebut bekerja sesuai namanya: flag itu menghapus perlindungan, lalu pip menulis ke /usr/local/lib/python3.12/dist-packages, yang berada sebelum direktori apt di sys.path. Versi Anda kemudian menimpa versi distribusi untuk setiap skrip sistem yang berjalan di bawah /usr/bin/python3, sedangkan apt tetap menganggap versinya sendiri terpasang. Akibatnya, tidak ada yang mendeteksi konflik sampai terjadi kerusakan. Di dalam container image yang selalu Anda build ulang dari awal, dampaknya terbatas pada image tersebut sehingga pendekatan ini masih dapat dibenarkan. Pada mesin yang Anda kelola, buat venv. Itu hanya memerlukan satu perintah.

Di mana virtual environment sebaiknya berada di server?

Di dalam direktori aplikasi itu sendiri, sebagai /srv/myapp/.venv, dengan kepemilikan oleh deploy user dan service account hanya memiliki akses baca serta eksekusi. Gunakan satu venv untuk setiap aplikasi, karena venv bersama berarti upgrade untuk aplikasi pertama dapat merusak aplikasi kedua. Jangan memindahkan atau menyalin venv setelah dibuat. Setiap skrip entry point di direktori bin/ memiliki absolute path tersebut pada shebang line-nya, sehingga venv yang dipindahkan gagal dengan bad interpreter: No such file or directory. Hapus venv tersebut dan build ulang dari requirements.txt.

Mengapa service systemd saya gagal dengan ModuleNotFoundError?

Karena unit tersebut menjalankan interpreter yang bukan milik venv. Jalankan systemctl cat myapp dan baca ExecStart. Nilai tersebut harus menunjuk ke /srv/myapp/.venv/bin/python, atau ke console script dari direktori bin/ yang sama, menggunakan absolute path. Menjalankan source activate secara langsung dalam ExecStart tidak dapat berfungsi karena ExecStart bukan shell, dan systemd melaporkan Failed to locate executable source dengan status=203/EXEC. Tambahkan Environment=PATH=/srv/myapp/.venv/bin:/usr/local/bin:/usr/bin:/bin agar setiap subprocess yang dijalankan kode Anda juga menemukan tools milik venv.

Apakah saya sebaiknya menggunakan uv, bukan venv dan pip?

Gunakan uv jika Anda memerlukan lockfile, jika waktu instalasi cukup lama hingga mengganggu, atau jika Anda memerlukan versi Python yang tidak dirilis oleh distribusi Anda. uv membuat venv biasa, sehingga unit systemd dan tata letak file tidak berubah, dan uv sync --frozen menginstal tepat apa yang dicatat oleh lockfile. Jika satu aplikasi di-deploy dari git dengan requirements.txt yang dipatok dan instalasi selesai dalam hitungan detik, python3 -m venv sudah memadai, serta Anda memiliki satu binary lebih sedikit yang perlu diperbarui di server.

#python#venv#pipx#uv#deployment