SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-13

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

Pilihan antara Python venv, pipx, dan uv pada server bergantung pada satu pertanyaan: apa yang Anda instal? Dependensi aplikasi harus ditempatkan dalam virtual environment di direktori aplikasi tersebut. Alat command-line yang ingin Anda jalankan berdasarkan namanya harus ditempatkan dalam pipx. uv dapat melakukan kedua hal tersebut dan menambahkan lockfile, yang mulai penting ketika mesin kedua harus membangun environment yang sama. Tidak satu pun dari ketiganya menginstal ke Python sistem, karena server Ubuntu versi terbaru menolaknya secara langsung.

Jalankan sudo pip install requests pada Ubuntu 24.04, lalu 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.

Inilah PEP 668 (Python enhancement proposal 668, "externally managed environments") yang bekerja 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 interpreter sistem, 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 menutupi salinan yang diinstal apt untuk setiap program pada server yang berjalan menggunakan /usr/bin/python3, termasuk tool bawaan distribusi. cloud-init mengimpor requests, jinja2, dan PyYAML dari interpreter tersebut. Jika salah satunya di-upgrade dengan pip dan menghasilkan release yang tidak kompatibel, sesuatu yang tidak pernah Anda ubah dapat gagal saat boot berikutnya dengan traceback yang menyebutkan package yang tidak Anda ketahui berada dalam rantai dependensi. apt tetap mencatat versinya sendiri sebagai versi yang terinstal, sehingga tidak ada peringatan, dan perbaikannya adalah sudo apt reinstall python3-requests.

Aturan yang berlaku berikutnya singkat. Python sistem adalah milik distribusi. Jangan menginstal apa pun ke dalamnya, jangan meng-upgrade library-nya dengan pip, dan jangan menghapus file EXTERNALLY-MANAGED hanya agar pesan tersebut hilang. Satu-satunya tugas yang perlu diberikan kepada /usr/bin/python3 adalah membangun 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. Karena itu, pada image minimal, percobaan pertama gagal dengan pesan yang menyebutkan secara tepat komponen yang belum tersedia.

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

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 apa pun yang Anda ekspor ke shell. Pastikan 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, berarti Anda 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 script di bin/ memiliki baris shebang absolut: head -1 /srv/myapp/.venv/bin/pip membaca #!/srv/myapp/.venv/bin/python. Jika Anda mengganti nama direktori induknya, script tersebut gagal dengan bad interpreter: No such file or directory. Venv juga mengunci interpreter yang membuatnya. Informasi ini dicatat sebagai baris home di /srv/myapp/.venv/pyvenv.cfg, dan bin/python3 merupakan symlink ke binary tersebut. Jika Anda meng-upgrade release sehingga python3.12 tidak lagi tersedia, symlink tidak memiliki target dan service gagal saat start dengan No such file or directory. Kedua kasus tersebut memiliki solusi yang sama: hapus venv dan buat yang baru dari requirements.txt. Proses pembuatan ulang hanya memerlukan 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 memasang aplikasi, bukan library. Setiap alat mendapatkan environment sendiri di bawah ~/.local/share/pipx/venvs/<name>, lalu 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 tepat setelah instalasi biasanya berarti Anda belum logout lalu login kembali. ~/.profile bawaan Ubuntu hanya menambahkan ~/.local/bin jika direktori tersebut sudah ada saat login. Karena itu, masalah ini biasanya hanya terjadi sekali pada akun baru.

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

No apps associated with package requests or its dependencies.

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

Hal yang penting pada server adalah lokasi instalasi. pipx install biasa menempatkan semua file di direktori home satu pengguna. Unit systemd yang berjalan sebagai myapp tidak dapat mengaksesnya. Cron job root juga tidak dapat mengaksesnya. sudo juga tidak akan menemukannya, karena secure_path dalam /etc/sudoers mengganti PATH dengan daftar tetap. Jika suatu alat harus tersedia di seluruh mesin, pasang 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 ada 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. pipx versi lama akan menampilkan unrecognized arguments: --global. Pada versi tersebut, tetapkan sendiri dua direktori yang didokumentasikan:

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

command -v ansible seharusnya menampilkan /usr/local/bin/ansible. Jika yang ditampilkan adalah path di bawah /home, alat tersebut dipasang di akun satu pengguna dan tidak akan ditemukan oleh service mana pun.

uv saat Anda memerlukan lockfile

uv adalah satu binary dari Astral yang mencakup fungsi pip, venv, dan pip-tools. uv juga dapat mengunduh interpreter. Kinerjanya cukup cepat sehingga perbedaannya terlihat pada VPS kecil, dan uv 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 script ke shell pada server perlu dilakukan dengan hati-hati. 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 satu binary mandiri yang tidak memiliki dependensi Python sendiri. Karena itu, menyalinnya ke /usr/local/bin adalah cara yang valid untuk membagikannya kepada semua user pada server.

Untuk project dengan pyproject.toml, alurnya terdiri atas empat command. Hanya command 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 tepat. Anda melakukan commit terhadap file tersebut bersama kode Anda. uv sync membuat .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. Perilaku ini sesuai untuk deployment. --no-dev tidak menyertakan grup dependensi development.

Project requirements.txt yang sudah ada tidak perlu dikonversi 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. .venv/bin/python berperilaku persis seperti saat python3 -m venv membuatnya. Karena itu, tidak ada bagian lain dalam panduan ini yang perlu diubah.

Ada satu default uv yang perlu diketahui sebelum digunakan pada server. Setting python-preference secara default bernilai managed. Dokumentasi menjelaskan bahwa nilai ini memilih interpreter "yang diunduh dan diinstal oleh uv", bukan interpreter yang sudah tersedia pada sistem. Jadi, uv venv --python 3.13 pada server yang hanya menyediakan 3.12 akan mengunduh 3.13 ke ~/.local/share/uv/python tanpa menampilkan kegagalan. Hal ini praktis pada laptop, tetapi dapat mengejutkan pada server karena service Anda kini bergantung pada interpreter di direktori home yang tidak akan pernah di-patch 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 luar 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, lalu mengubah prompt. Skrip ini tidak berisi apa pun yang dibaca oleh interpreter. Aktivasi hanya memudahkan pengguna yang mengetik python pada prompt.

Environment sebenarnya dipilih berdasarkan 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, Python menetapkan sys.prefix ke venv. Hal ini menempatkan site-packages milik venv pada sys.path. Itulah seluruh mekanismenya. Mekanisme ini tidak memerlukan environment variable atau shell.

Oleh karena itu, unit berikut tidak pernah start:

[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 mengeksekusi program secara langsung. Tidak ada builtin source, && diteruskan sebagai argumen literal, dan tidak ada yang diekspansi.

Unit berikut start, lalu berhenti:

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

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

Membungkus perintah dengan /bin/bash -c 'source ... && gunicorn ...' memang berfungsi. Namun, cara ini menambahkan shell di antara systemd dan proses Anda tanpa manfaat. Satu absolute path 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 melaporkan active (running) dengan Main PID yang merupakan proses gunicorn Anda. Jika hasilnya berbeda, baca journal.

Baris Environment=PATH= tidak ditujukan untuk ExecStart, karena ExecStart sudah memiliki full path. Baris tersebut diperlukan oleh proses yang dijalankan oleh aplikasi Anda. Service mewarisi PATH default yang singkat dari systemd. Akibatnya, kode Python yang memanggil subprocess.run(["ffmpeg", ...]) atau management command yang menjalankan console script dari venv tidak akan menemukan yang diperlukan. Menempatkan direktori bin milik venv di bagian awal activate adalah satu-satunya bagian yang benar-benar digunakan service. Periksa nilai yang benar-benar diterima unit dengan systemctl show -p Environment myapp.

Aturan yang sama berlaku untuk pekerjaan terjadwal. cron menjalankan job dengan PATH sebesar /usr/bin:/bin. Karena itu, baris crontab yang berisi python3 /srv/myapp/cleanup.py menjalankan interpreter sistem dan gagal dengan ModuleNotFoundError pada pukul tiga pagi. Error tersebut dikirim ke local mail spool yang tidak dibaca siapa pun. Tuliskan absolute path venv di sana juga. Untuk mengirim output tersebut ke journal dan menyimpan catatan eksekusi terakhir, pasangan service dan timer systemd menggunakan 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 menggunakan pip install dengan --break-system-packages?

Tidak pada server yang harus tetap beroperasi. Flag tersebut melakukan persis seperti namanya: menghapus perlindungan, lalu pip menulis ke /usr/local/lib/python3.12/dist-packages, yang berada sebelum direktori apt pada sys.path. Versi Anda kemudian mengesampingkan versi distribusi untuk setiap skrip sistem yang berjalan di bawah /usr/bin/python3. Sementara itu, apt tetap menganggap versinya sendiri terpasang. Karena itu, tidak ada yang mendeteksi konflik tersebut sampai terjadi kerusakan. Dalam image container yang selalu Anda bangun ulang dari awal, dampaknya terbatas pada image tersebut, sehingga penggunaan flag ini masih dapat dibenarkan. Pada mesin yang Anda kelola, buat venv. Hanya diperlukan satu perintah.

Di mana virtual environment sebaiknya ditempatkan pada server?

Tempatkan di dalam direktori aplikasi itu sendiri, sebagai /srv/myapp/.venv, dan miliki oleh pengguna deploy. Akun service hanya boleh memiliki akses baca dan eksekusi. Gunakan satu venv untuk setiap aplikasi. venv bersama berarti upgrade untuk aplikasi pertama dapat merusak aplikasi kedua. Jangan memindahkan atau menyalin venv setelah dibuat. Setiap skrip dalam direktori bin/ memiliki path absolut tersebut pada baris shebang-nya, sehingga venv yang dipindahkan akan gagal dengan bad interpreter: No such file or directory. Hapus venv tersebut dan bangun 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. Perintah tersebut harus menyebut /srv/myapp/.venv/bin/python atau console script dari direktori bin/ yang sama, menggunakan path absolut. Sourcing activate dalam file unit tidak dapat berfungsi karena ExecStart bukan shell. systemd akan 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 tool milik venv.

Apakah sebaiknya saya 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. uv sync --frozen memasang persis apa yang tercatat dalam lockfile. Jika satu aplikasi di-deploy dari git dengan requirements.txt yang dipatok dan instalasinya selesai dalam hitungan detik, python3 -m venv sudah memadai. Dengan demikian, Anda tidak perlu memelihara satu binary tambahan agar tetap diperbarui di server.

#python#venv#pipx#uv#deployment