Kalau Anda pernah mengucapkan kalimat "di laptop saya jalan", Rekan ArtonLabs, tulisan ini mungkin akan terasa sangat akrab.
Bug paling menyebalkan yang kami tangani tahun ini bukan bug di kode, melainkan di berkas dependensi. Sebuah aplikasi analisis berjalan benar di laptop pengembangnya dan salah hitung di server, karena requirements.txt-nya hanya menulis pandas>=2.2, dan pip di server menarik pandas versi mayor berikutnya yang mengubah satuan waktu bawaan. Tidak ada galat, hanya angka yang salah. Tulisan ini tentang cara memastikan itu tidak terjadi pada Anda.
Daftar Isi
- Masalahnya: Batas Bawah Versi Bukan Kunci
- Virtualenv: Satu Proyek, Satu Lingkungan
- Dua Berkas: Yang Anda Inginkan dan Yang Terpasang
- Membuat Lock File dengan pip-tools
- pyproject.toml dan Ekstra Opsional
- Memasang di Produksi
- Memperbarui dengan Sadar
- Penutup
Masalahnya: Batas Bawah Versi Bukan Kunci
django>=5.0 berarti "versi 5.0 atau apa pun yang lebih baru". Hari ini itu mungkin 5.2; enam bulan lagi mungkin 6.0 dengan perubahan yang merusak. Dua pemasangan pada waktu berbeda menghasilkan pohon dependensi berbeda, dan tak seorang pun menyadarinya sampai sesuatu berperilaku aneh. Yang lebih licik adalah dependensi tidak langsung: Anda tidak pernah menulis numpy di berkas, tetapi pandas membawanya, dan versinya ikut berubah.
Untuk pengembangan, batas bawah itu wajar; Anda memang ingin mengikuti pembaruan. Untuk produksi, itu liabilitas.
Virtualenv: Satu Proyek, Satu Lingkungan
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
Setiap proyek mendapat folder .venv sendiri, terpisah dari Python sistem dan dari proyek lain. Jangan pernah pip install ke Python sistem di server; Ubuntu modern bahkan menolaknya secara bawaan. Tambahkan .venv/ ke .gitignore. Di VinzaPanel, virtualenv per aplikasi dibuatkan otomatis oleh plugin deploy, tetapi prinsipnya sama.
Dua Berkas: Yang Anda Inginkan dan Yang Terpasang
Pisahkan dua hal. Berkas pertama menyatakan apa yang proyek Anda butuhkan secara langsung, dengan batas longgar: django>=5.0,<6, pandas>=2.2. Berkas kedua, lock file, mencatat persis versi setiap paket yang terpasang, termasuk yang tidak langsung, lengkap dengan hash. Berkas pertama untuk manusia; berkas kedua untuk server.
Membuat Lock File dengan pip-tools
pip install pip-tools
# requirements.in berisi dependensi langsung dengan batas longgar
pip-compile --generate-hashes -o requirements.lock requirements.in
Hasilnya berkas requirements.lock berisi versi persis, misalnya pandas==2.2.3, beserta hash setiap berkas wheel. Commit kedua berkas ke Git. Ketika ingin memperbarui, jalankan pip-compile --upgrade atau pip-compile --upgrade-package pandas untuk satu paket saja, periksa perubahannya di diff, uji, baru commit. Pembaruan menjadi keputusan sadar, bukan kejadian acak saat deploy.
Alat lain yang melakukan hal serupa: uv (sangat cepat, punya uv lock), Poetry, dan PDM. Pilih satu dan konsisten; yang penting ada lock file yang di-commit.
pyproject.toml dan Ekstra Opsional
Untuk proyek yang juga berupa paket, deklarasikan dependensi di pyproject.toml. Satu pola yang kami pakai di aplikasi riset: dependensi inti kecil, dan dependensi berat seperti torch atau transformers di ekstra opsional:
[project]
name = "analisis"
dependencies = ["django>=5.0,<6", "pandas>=2.2", "scikit-learn>=1.5"]
[project.optional-dependencies]
ml = ["torch", "transformers"]
dev = ["pytest", "pip-tools"]
Server web memasang pip install -e . --no-deps setelah lock file, tanpa menarik torch yang berukuran gigabyte. Mesin pelatihan memasang .[ml]. Kode memuat pustaka berat secara malas (lazy import) dan jatuh ke jalur tanpa pustaka itu kalau tidak ada. Dengan begitu, venv produksi kami turun dari lebih dari satu gigabyte menjadi beberapa ratus megabyte.
Memasang di Produksi
.venv/bin/pip install --require-hashes -r requirements.lock
.venv/bin/pip install -e . --no-deps # kalau proyek berupa paket
--require-hashes membuat pip menolak berkas yang hash-nya tidak cocok, pengaman terhadap paket yang diganti di tengah jalan. Dan --no-deps pada baris kedua memastikan pemasangan paket proyek tidak diam-diam menarik versi lain dari yang sudah dikunci.
Setelah pemasangan, simpan keluaran pip freeze ke log deploy. Saat sesuatu aneh terjadi seminggu kemudian, Anda bisa membandingkan persis apa yang berubah.
Memperbarui dengan Sadar
Jadwalkan pembaruan, misalnya sebulan sekali: pip-compile --upgrade, jalankan seluruh uji, baca catatan rilis paket yang melompat versi mayor, lalu deploy ke salinan uji dulu. Pembaruan keamanan mendesak bisa dilakukan per paket dengan --upgrade-package. Alat seperti pip-audit memeriksa apakah versi yang terkunci punya kerentanan yang diketahui:
pip install pip-audit
pip-audit -r requirements.lock
Kalau repositori ada di GitHub atau GitLab, Dependabot atau Renovate bisa membuka pull request otomatis setiap kali ada versi baru, lengkap dengan catatan rilisnya. Anda tetap yang memutuskan menggabungkannya, tetapi pekerjaan mengecek satu per satu hilang, dan pembaruan keamanan tidak lagi tertunda berbulan-bulan hanya karena tidak ada yang sempat melihat.
Lock file bukan alasan untuk tidak pernah memperbarui; ia alasan untuk memperbarui dengan mata terbuka.
Dependensi Sistem dan Versi Python
Lock file mengunci paket Python, tetapi tidak mengunci versi Python itu sendiri atau pustaka sistem yang dibutuhkan saat membangun wheel. Nyatakan versi Python di pyproject.toml dengan requires-python = ">=3.12,<3.14" dan di berkas .python-version untuk alat seperti uv atau pyenv. Untuk paket yang butuh kompilasi (psycopg, lxml, Pillow), pastikan server punya python3-dev dan build-essential, atau lebih baik lagi, pilih versi yang menyediakan wheel biner untuk arsitektur server Anda sehingga tidak ada yang perlu dikompilasi. pip install --only-binary=:all: akan gagal keras kalau ada paket yang memaksa kompilasi, dan itu sinyal yang berguna sebelum deploy.
Mereproduksi Lingkungan Produksi di Laptop
Ketika bug hanya muncul di server, langkah pertama kami adalah membuat venv baru dari lock file yang sama, dengan versi Python yang sama, lalu menjalankan uji di situ. Sembilan dari sepuluh kali, bugnya langsung muncul, dan itu berarti masalahnya ada di versi paket, bukan di server. Kalau tetap tidak muncul, barulah curiga pada data, variabel lingkungan, atau pustaka sistem. Simpan juga keluaran python --version dan pip freeze dari server di repositori, misalnya di deploy/frozen.txt, sebagai potret yang bisa dibandingkan kapan saja.
Terima kasih sudah membaca. Satu lock file yang di-commit hari ini menyelamatkan banyak jam di masa depan; semoga Anda tidak perlu membuktikannya dengan cara yang sulit.