SSD Nodes Learn 🎉 VPS $5.50/महिन्यापासून
मार्गदर्शक Matt Connorद्वारे Matt Connor · अपडेटेड 2026-08-13

Ubuntu सर्व्हरवर Python venv, pipx आणि uv निवड कशी करावी?

नवीन Ubuntu सर्व्हरवर externally-managed-environment एरर का येते ते जाणून घ्या. तुमच्या गरजेनुसार venv, pipx किंवा uv पैकी योग्य पर्याय निवडून सिस्टिमd सह वापरण्याची पद्धत शिका.

नवीन Ubuntu सर्व्हरवर pip install का अपयशी ठरते

सर्व्हरवर Python venv, pipx आणि uv यांपैकी कशाची निवड करायची, हे एका प्रश्नावर अवलंबून आहे: तुम्ही नक्की काय इन्स्टॉल करत आहात? ॲप्लिकेशनच्या डिपेंडन्सीज त्या ॲप्लिकेशनच्या स्वतःच्या डिरेक्टरीमधील व्हर्च्युअल एन्व्हायरनमेंटमध्ये असाव्यात. ज्या कमांड-लाइन टूल्सची नावे तुम्हाला थेट टाईप करायची आहेत, ती pipx मध्ये असावीत. uv ही दोन्ही कामे करते आणि एक lockfile जोडते, जी जेव्हा दुसऱ्या मशीनवर तेच एन्व्हायरनमेंट तयार करायचे असते, तेव्हा महत्त्वाची ठरते. यापैकी कोणतेही टूल सिस्टिम Python मध्ये इन्स्टॉल करत नाही, कारण सध्याचा Ubuntu सर्व्हर तसे करण्यास थेट नकार देतो.

Ubuntu 24.04 वर sudo pip install requests रन करा, म्हणजे pip एकही फाईल डाऊनलोड करण्यापूर्वीच थांबेल.

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.

हे PEP 668 (Python enhancement proposal 668, "externally managed environments") चे काम आहे. Debian आणि Ubuntu हे /usr/lib/python3.12/EXTERNALLY-MANAGED वर इंटरप्रिटरच्या बाजूला एक मार्कर फाईल ठेवतात आणि ज्या इंटरप्रिटरमध्ये ही फाईल असते, त्यात काहीही लिहिण्यास pip नकार देते.

हा नियम sys.path क्रमानुसार अस्तित्वात आहे. apt लायब्ररीज /usr/lib/python3/dist-packages मध्ये इन्स्टॉल करते. सिस्टिम इंटरप्रिटरवर root म्हणून चालवलेले pip, /usr/local/lib/python3.12/dist-packages मध्ये लिहिते आणि Debian चे पॅकेजिंग त्या डिरेक्टरीला सर्च पाथमध्ये आधी ठेवते. python3 -c 'import sys; print(sys.path)' वापरून तुम्ही स्वतः हा क्रम पाहू शकता. त्यामुळे, सिस्टिमवरील /usr/bin/python3 अंतर्गत चालणाऱ्या प्रत्येक प्रोग्रामसाठी, pip ने लिहिलेली कॉपी apt ने इन्स्टॉल केलेल्या कॉपीवर ओव्हरराईड (shadow) करते, ज्यामध्ये डिस्ट्रिब्युशनची स्वतःची टूल्सही समाविष्ट आहेत. cloud-init हे त्या इंटरप्रिटरमधून requests, jinja2 आणि PyYAML इम्पोर्ट करते. pip वापरून यापैकी एकाचे अपग्रेड केल्यास आणि एखादी विसंगत (incompatible) रिलीज आल्यास, तुम्ही कधीही स्पर्श न केलेली एखादी गोष्ट पुढच्या बूट वेळी अपयशी ठरेल आणि त्यात अशा पॅकेजचे नाव असेल जे त्या साखळीत आहे हे तुम्हाला माहितही नसेल. apt कडे अजूनही स्वतःची व्हर्जन इन्स्टॉल असल्याचे नोंदवलेले असते, त्यामुळे कोणतीही चेतावणी मिळत नाही आणि दुरुस्ती करणे sudo apt reinstall python3-requests इतके कठीण होते.

याचा नियम अगदी साधा आहे. सिस्टिम Python हे डिस्ट्रिब्युशनच्या मालकीचे असते. त्यात काहीही इन्स्टॉल करू नका, pip वापरून त्याच्या लायब्ररीज अपग्रेड करू नका आणि तो मेसेज घालवण्यासाठी EXTERNALLY-MANAGED फाईल डिलीट करू नका. /usr/bin/python3 ला फक्त व्हर्च्युअल एन्व्हायरनमेंट तयार करण्याचे काम द्या.

venv विरुद्ध pipx विरुद्ध uv: निर्णय घेण्याचे नियम

तुम्ही काय इन्स्टॉल करत आहात यावर आधारित साधन निवडा, केवळ अलीकडे वाचलेल्या माहितीवर आधारित नको.

  • एखादे ॲप्लिकेशन जे तुम्ही सर्व्हिस म्हणून डिप्लॉय आणि रन करता, जसे की Django किंवा Flask प्रोजेक्ट: त्या ॲप्लिकेशनच्या डिरेक्टरीमध्ये एक व्हर्च्युअल एन्व्हायरनमेंट (venv) वापरा.
  • एखादे कमांड-लाइन टूल जे तुम्हाला तुमच्या PATH वर हवे आहे, जसे की ansible किंवा httpie: pipx वापरा, जे प्रत्येक टूलला एक खाजगी एन्व्हायरनमेंट आणि PATH वर एक लिंक देते.
  • असा प्रोजेक्ट ज्याला lockfile, जलद इन्स्टॉलेशन किंवा अशी Python आवृत्ती हवी आहे जी डिस्ट्रिब्युशनमध्ये उपलब्ध नाही: uv वापरा, जे एक सामान्य venv आणि एक uv.lock फाईल तयार करते.
  • अशी लायब्ररी जी तुमच्या कोडसाठी नसून डिस्ट्रिब्युशन टूलसाठी आवश्यक आहे: sudo apt install python3-<name> वापरा, सिस्टम इंटरप्रिटरमध्ये काहीही जोडण्याचा हा एकमेव समर्थित मार्ग आहे.

pipx आणि uv tool install एकाच प्रकारचे काम करतात, त्यामुळे ज्या मशीनवर आधीच uv आहे, तिथे pipx ची वेगळी गरज नाही. तुम्ही कोणते वेब फ्रेमवर्क निवडले आहे, याने यात काहीही फरक पडत नाही: VPS वर Django आणि Flask हे requirements.txt मध्ये काय साठवले जाते यावर अवलंबून असतात, ते एन्व्हायरनमेंट कसे तयार केले जाते यावर नाही. खालील सर्व उदाहरणे Ubuntu 24.04 आणि त्यातील Python 3.12 साठी आहेत, त्यामुळे जर तुमची आवृत्ती वेगळी असेल तर पाथ (paths) मधील आवृत्ती तशी बदलून घ्या.

प्रत्येक ॲप्लिकेशनसाठी venv तयार करा

Ubuntu मध्ये venv मॉड्यूल हे मूळ Python पॅकेजपासून वेगळे केलेले असते, त्यामुळे किमान (minimal) इमेजवर पहिल्या प्रयत्नातच नेमके काय गहाळ आहे हे सांगणारा संदेश मिळून प्रक्रिया अपयशी ठरते.

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

ते इन्स्टॉल करा आणि त्यानंतर ज्या वापरकर्त्याच्या मालकीचा कोड असेल, त्याच्या नावाने एनवायरमेंट तयार करा.

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

काय उपलब्ध नाही ते पहा: तिथे source नाही, activate नाही. /srv/myapp/.venv/bin/pip त्या एनवायरमेंटमध्ये इन्स्टॉल होते कारण बायनरी कुठे आहे हे महत्त्वाचे असते, तुम्ही शेलमध्ये काय एक्सपोर्ट केले आहे हे नाही. पुढे जाण्यापूर्वी याची खात्री करा.

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

हे /srv/myapp/.venv प्रिंट करते. जर ते /usr प्रिंट करत असेल, तर तुम्ही सिस्टम इंटरप्रिटर वापरत आहात आणि तुमची पॅकेजेस अशा ठिकाणी गेली आहेत जिथे तुम्हाला ती हवी नव्हती.

venv चे दोन गुणधर्म ठरवतात की तुम्ही त्यानंतर त्यासोबत काय करू शकता. venv हे एका ठिकाणाहून दुसऱ्या ठिकाणी हलवता (relocatable) येत नाही, कारण bin/ मधील प्रत्येक स्क्रिप्टमध्ये एक ॲब्सोल्युट shebang लाईन असते: head -1 /srv/myapp/.venv/bin/pip हे #!/srv/myapp/.venv/bin/python वाचते. पालक डिरेक्टरीचे नाव बदलल्यास या स्क्रिप्ट्स bad interpreter: No such file or directory मुळे अपयशी ठरतात. venv ज्या इंटरप्रिटरने तयार केले आहे त्यालाच पिन करते, जे /srv/myapp/.venv/pyvenv.cfg मधील home लाईनमध्ये नोंदवलेले असते आणि bin/python3 हे त्या बायनरीचे सिम्बॉलिक लिंक असते. जर तुम्ही रिलीज अपग्रेड केली आणि python3.12 निघून गेले, तर सिम्बॉलिक लिंकला कोणतेही टार्गेट उरत नाही आणि सर्व्हिस सुरू होतानाच No such file or directory मुळे बंद पडते. दोन्ही प्रकरणांवर एकच उपाय आहे: venv डिलीट करा आणि requirements.txt पासून नवीन तयार करा. पुन्हा तयार करायला काही सेकंद लागतात. एका मशीनवरून दुसऱ्या मशीनवर कधीही venv कॉपी करू नका.

venv कुठे असावा आणि त्याचा मालक कोण असावा

venv ला /srv/myapp/.venv वरील कोडच्या शेजारी ठेवा आणि प्रत्येक ॲप्लिकेशनसाठी एक स्वतंत्र venv वापरा. यामुळे डिप्लॉयमेंट एकाच डिरेक्टरीमध्ये मर्यादित राहते, systemd युनिटला कधीही न बदलणारा पाथ मिळतो आणि एका सामायिक डिपेंडन्सी अपडेटमुळे दोन ॲप्लिकेशन्स एकमेकांमध्ये अडथळा निर्माण करत नाहीत. venv ला अशा कोणत्याही ठिकाणी ठेवू नका जिथे तुमचा वेब सर्व्हर थेट फाइल्स पब्लिश करतो, कारण त्यात तुमच्या डिपेंडन्सीज आणि अनेकदा कॉन्फिगरेशन असते.

मालकी हक्काकडे अर्धा मिनिट लक्ष देणे आवश्यक आहे. कोड आणि एनवायरमेंटची मालकी एका deploy युजरकडे राहू द्या आणि सर्व्हिस अकाउंटला फक्त वाचण्याचे (read) आणि एक्झिक्युट करण्याचे (execute) अधिकार द्या.

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

आता सर्व्हिस आपल्या डिपेंडन्सीज इम्पोर्ट करू शकते, पण त्यामध्ये बदल करू शकत नाही. याचा अर्थ असा की, वेब ॲप्लिकेशनमधील कोड एक्झिक्युशन बग डिस्कवरील लायब्ररीला गुपचूप बदलू शकत नाही आणि रीस्टार्टनंतरही टिकून राहू शकत नाही. मशीनच्या उर्वरित भागासाठी हाच तर्क कमीतकमी अधिकारांसह सर्व्हिसेस चालवणे यामध्ये स्पष्ट केला आहे.

कमांड-लाइन टूल्ससाठी pipx

pipx लायब्ररी नाही, तर ॲप्लिकेशन्स इन्स्टॉल करते. प्रत्येक टूलला ~/.local/share/pipx/venvs/<name> अंतर्गत स्वतःचे स्वतंत्र एनवायरमेंट मिळते आणि त्या टूलच्या एक्झिक्युटेबल्स ~/.local/bin मध्ये लिंक केल्या जातात. त्यामुळे, एकाच लायब्ररीच्या वेगवेगळ्या व्हर्जन्सची गरज असलेल्या दोन टूल्समध्ये कधीही संघर्ष होत नाही.

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

pipx ensurepath तुमच्या शेल स्टार्टअप फाईलमध्ये बदल करून ~/.local/bin ला PATH मध्ये जोडते. हे सध्या सुरू असलेल्या शेलमध्ये बदल करू शकत नाही, त्यामुळे इन्स्टॉलेशननंतर लगेच http: command not found एरर येण्याचा अर्थ असा की तुम्ही अजून लॉग आउट करून पुन्हा लॉग इन केलेले नाही. Ubuntu चे डीफॉल्ट ~/.profile फक्त तेव्हाच ~/.local/bin जोडते जेव्हा लॉग इन करताना ती डिरेक्टरी अस्तित्वात असते. म्हणूनच नवीन अकाउंटवर ही समस्या एकदाच येते आणि पुन्हा उद्भवत नाही.

जर तुम्ही pipx ला एखादी लायब्ररी इन्स्टॉल करायला सांगितली, तर ते नकार देते आणि खालील संदेश दाखवते:

No apps associated with package requests or its dependencies.

हे टूल तुम्हाला सांगत आहे की हे त्यासाठी योग्य साधन नाही. लायब्ररी ॲप्लिकेशनच्या venv मध्येच असाव्यात.

सर्व्हरवर महत्त्वाचा मुद्दा म्हणजे लोकेशन. साधे pipx install सर्व काही एका युजरच्या होम डिरेक्टरीमध्ये ठेवते. myapp म्हणून चालणारी systemd युनिट ती पाहू शकत नाही, root क्रॉन जॉब ती पाहू शकत नाही आणि sudo ला सुद्धा ती सापडणार नाही, कारण /etc/sudoers मधील secure_path हे PATH ला एका निश्चित यादीने बदलून टाकते. संपूर्ण मशीनसाठी आवश्यक असलेल्या टूलसाठी, ते ग्लोबली इन्स्टॉल करा.

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

--global फ्लॅग एनवायरमेंटला /opt/pipx मध्ये ठेवते आणि एक्झिक्युटेबल्सना /usr/local/bin मध्ये लिंक करते, जे डीफॉल्ट PATH वर आणि secure_path च्या आत असते. आधी pipx --version वापरून तुमचे व्हर्जन तपासा, कारण Ubuntu 24.04 मध्ये pipx 1.4.3 पॅकेज केलेले आहे, जे --global पेक्षा जुने आहे आणि जुने pipx unrecognized arguments: --global ने उत्तर देते. त्या व्हर्जनवर, खालील दोन डिरेक्टरीज स्वतः सेट करा:

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

command -v ansible ने /usr/local/bin/ansible प्रिंट केले पाहिजे. जर ते /home अंतर्गत एखादा पाथ प्रिंट करत असेल, तर टूल एका युजरच्या अकाउंटमध्ये गेले आहे आणि कोणतीही सर्व्हिस ते शोधू शकणार नाही.

जेव्हा तुम्हाला lockfile हवी असते तेव्हा uv

uv हे Astral कडून मिळालेले एक सिंगल बायनरी आहे, जे pip, venv आणि pip-tools ची कामे करते. हे इंटरप्रिटर्स देखील डाउनलोड करू शकते. हे इतके वेगवान आहे की लहान VPS वरही त्याचा फरक जाणवतो आणि ते एक अचूक lockfile तयार करते.

अधिकृत इंस्टॉलर uv आणि uvx ला ~/.local/bin मध्ये ठेवते:

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

सर्व्हरवर स्क्रिप्ट थेट शेलमध्ये पाइप करताना थोडी काळजी घेणे आवश्यक आहे. URL मधील व्हर्जन पिन करा आणि चालवण्यापूर्वी फाइल वाचा:

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

जर pipx आधीच उपलब्ध असेल, तर pipx install uv देखील काम करते. uv ही एक स्वयंपूर्ण बायनरी आहे, ज्याला स्वतःच्या कोणत्याही Python डिपेंडन्सीची गरज नसते. त्यामुळे, ती /usr/local/bin मध्ये कॉपी करणे हा सर्व वापरकर्त्यांना ती उपलब्ध करून देण्याचा एक वैध मार्ग आहे.

pyproject.toml असलेल्या प्रोजेक्टसाठी, वर्कफ्लोमध्ये चार कमांड्स असतात आणि त्यातील शेवटची कमांड सर्व्हरवर चालवली जाते.

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

uv lock हे uv.lock तयार करते, जी एक क्रॉस-प्लॅटफॉर्म lockfile आहे. यामध्ये अचूक रिझॉल्व्ह केलेली व्हर्जन्स असतात, जी तुम्ही तुमच्या कोडसोबत कमिट करता. uv sync हे प्रोजेक्ट रूटमध्ये .venv तयार करते जे या फाइलशी जुळते. सर्व्हरवर, --frozen हा फ्लॅग महत्त्वाचा आहे: डॉक्युमेंटेशननुसार, हे lockfile मधील व्हर्जन्सनाच सत्य मानून काम करते, lockfile अपडेट आहे की नाही हे तपासत नाही. डिप्लॉयमेंटसाठी हेच अपेक्षित असते. --no-dev डेव्हलपमेंट डिपेंडन्सी ग्रुपला वगळते.

विद्यमान requirements.txt प्रोजेक्टला कोणत्याही रूपांतरणाची गरज नाही, कारण uv हे pip ची भाषा समजते:

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

या प्रक्रियेतून एक सामान्य व्हर्च्युअल एन्व्हायर्नमेंट तयार होते. .venv/bin/python अगदी तसेच काम करते जसे python3 -m venv ने तयार केलेले एन्व्हायर्नमेंट करते, त्यामुळे या मार्गदर्शिकेतील पुढील कोणत्याही गोष्टी बदलत नाहीत.

सर्व्हरवर uv वापरण्यापूर्वी त्याच्या एका डीफॉल्ट सेटिंगबद्दल माहिती असणे आवश्यक आहे. त्याचे python-preference सेटिंग डीफॉल्टनुसार managed असते. याचा अर्थ असा की, सिस्टमवर आधीपासून असलेल्या इंटरप्रिटरपेक्षा "uv द्वारे डाउनलोड आणि इंस्टॉल केलेले" इंटरप्रिटर निवडले जातात. त्यामुळे, ज्या बॉक्सवर फक्त 3.12 आहे, तिथे uv venv --python 3.13 कमांड दिल्यास ते एरर न देता शांतपणे 3.13 ला ~/.local/share/uv/python मध्ये डाउनलोड करते. लॅपटॉपवर हे सोयीचे असले तरी सर्व्हरवर हे आश्चर्यकारक ठरू शकते, कारण तुमची सेवा आता अशा इंटरप्रिटरवर अवलंबून असते जो होम डिरेक्टरीमध्ये आहे आणि ज्याला apt upgrade कधीही पॅच करणार नाही. जर तुम्हाला डिस्ट्रिब्युशनचा इंटरप्रिटर वापरायचा असेल, तर uv.toml मध्ये python-preference ला only-system वर सेट करा. जर तुम्हाला एन्व्हायर्नमेंट प्रोजेक्ट रूटच्या बाहेर हवे असेल, तर UV_PROJECT_ENVIRONMENT द्वारे तुम्ही प्रोजेक्ट व्हर्च्युअल एन्व्हायर्नमेंटसाठी डिरेक्टरी निर्दिष्ट करू शकता.

systemd ला activate ऐवजी venv इंटरप्रिटरकडे निर्देशित करा

बहुतेक Python डिप्लॉयमेंट इथेच अयशस्वी होतात, कारण activate नक्की काय करते याबद्दल गैरसमज असतो.

bin/activate ही एक शेल स्क्रिप्ट आहे. ती venv ची bin डिरेक्टरी PATH च्या सुरुवातीला जोडते, VIRTUAL_ENV सेट करते, जुनी मूल्ये जतन करते जेणेकरून deactivate ती पुन्हा पूर्ववत करू शकेल आणि तुमचा प्रॉम्प्ट बदलते. यात अशी कोणतीही गोष्ट नसते जी इंटरप्रिटर स्वतः वाचतो. ॲक्टिव्हेशन ही केवळ प्रॉम्प्टवर python टाईप करणाऱ्या मानवासाठी सोय आहे.

कोणते वातावरण निवडले जाईल हे तुम्ही कोणता इंटरप्रिटर फाईल रन करता यावर अवलंबून असते. जेव्हा /srv/myapp/.venv/bin/python सुरू होते, तेव्हा Python चे site मॉड्यूल एक्झिक्युटेबल असलेल्या डिरेक्टरीमध्ये आणि त्याच्या एक स्तर वर pyvenv.cfg फाईल शोधते. /srv/myapp/.venv/pyvenv.cfg सापडल्यावर sys.prefix हे venv वर सेट होते, ज्यामुळे त्या venv ची site-packages ही sys.path वर येते. हीच संपूर्ण प्रक्रिया आहे. यासाठी कोणत्याही एन्व्हायर्नमेंट व्हेरिएबलची किंवा शेलची गरज नसते.

त्यामुळे हे युनिट कधीच सुरू होत नाही:

[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 ही शेल कमांड लाईन नाही. systemd थेट प्रोग्राम एक्झिक्युट करते, त्यामुळे तिथे कोणतेही source इनबिल्ट नसते, && हे अक्षरशः आर्ग्युमेंट म्हणून दिले जाते आणि कशाचाही विस्तार (expand) होत नाही.

आणि हे युनिट सुरू होते, पण लगेच बंद पडते:

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

/usr/bin/python3 हा सिस्टम इंटरप्रिटर आहे आणि त्याच्या sys.path मध्ये कधीही तुमचा venv नसतो. तीच कमांड तुमच्या SSH सेशनमध्ये काम करते कारण तुम्ही तिथे venv ॲक्टिव्हेट केले होते, त्यामुळे शेलने python3 ला PATH द्वारे .venv/bin/python3 मध्ये रिझॉल्व्ह केले होते.

कमांडला /bin/bash -c 'source ... && gunicorn ...' मध्ये रॅप करणे काम करते. पण यामुळे systemd आणि तुमच्या प्रोसेसच्या मध्ये विनाकारण एक शेल येतो. त्याऐवजी पूर्ण पाथ (absolute path) वापरणे अधिक योग्य ठरते:

[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 ने active (running) रिपोर्ट केले पाहिजे, ज्यामध्ये Main PID म्हणून तुमची gunicorn प्रोसेस दिसेल. जर काही वेगळे दिसत असेल, तर जर्नल वाचा.

Environment=PATH= लाईन ExecStart साठी नसते, कारण त्यात आधीच पूर्ण पाथ असतो. ती तुमच्या ॲप्लिकेशनने सुरू केलेल्या प्रोसेससाठी असते. सर्व्हिसला systemd कडून एक छोटा डीफॉल्ट PATH मिळतो, त्यामुळे Python कोड जो subprocess.run(["ffmpeg", ...]) कॉल करतो, किंवा venv मधील कन्सोल स्क्रिप्ट रन करणारी मॅनेजमेंट कमांड, तिला आवश्यक गोष्टी सापडत नाहीत. venv ची bin डिरेक्टरी सुरुवातीला ठेवणे हा activate चा एकमेव भाग आहे जो सर्व्हिस खरोखर वापरते. युनिटला नक्की काय मिळाले हे systemctl show -p Environment myapp वापरून तपासा.

हाच नियम शेड्युल केलेल्या कामांनाही लागू होतो. cron जॉब्स PATH म्हणून /usr/bin:/bin वापरून रन करते, त्यामुळे crontab लाईनमध्ये python3 /srv/myapp/cleanup.py लिहिल्यास ती सिस्टम इंटरप्रिटर रन करते आणि पहाटे तीन वाजता ModuleNotFoundError एररसह अयशस्वी होते, आणि ही एरर अशा लोकल मेल स्पूलमध्ये जाते जी कोणीही वाचत नाही. तिथेही venv चा पूर्ण पाथ लिहा. आउटपुट जर्नलमध्ये मिळवण्यासाठी आणि शेवटच्या रनचा रेकॉर्ड ठेवण्यासाठी, systemd सर्व्हिस आणि टायमर जोडी वापरणे अधिक चांगले, ज्यामध्ये तीच ExecStart लाईन वापरता येते.

Docker या निर्णयाची जागा घेते का?

कंटेनरची स्वतःची फाईलसिस्टम असते, त्यामुळे हा प्रश्न नाहीसा होण्याऐवजी त्याचे स्वरूप बदलते. python:3.12-slim सारख्या अधिकृत इमेजमध्ये, Python हे /usr/local मध्ये इन-बिल्ट असते आणि त्यात कोणताही EXTERNALLY-MANAGED मार्कर नसतो. त्यामुळे पॅकेज जोडण्यासाठी pip install चा root म्हणून वापर करणे हाच अपेक्षित मार्ग आहे आणि venv मुळे फारसा फायदा होत नाही. त्याऐवजी FROM ubuntu:24.04 बिल्ड केल्यास, तुम्हाला इमेजच्या आत पुन्हा externally-managed-environment चा सामना करावा लागतो. याचे कारण होस्टवरील कारणासारखेच आहे: तो डिस्ट्रिब्युशनचा इंटरप्रिटर असतो आणि त्यात डिस्ट्रिब्युशनची मार्कर फाईल असते.

अनेक इमेज अजूनही venv वापरतात, कारण त्यामुळे multi-stage build सोपे होते. बिल्डर स्टेज /opt/venv मध्ये इन्स्टॉल करते आणि रनटाइम स्टेज फक्त ती एक डिरेक्टरी कॉपी करून कंपायलर मागे सोडते. 'activate' ची समस्या त्यासोबतच येते. RUN source /opt/venv/bin/activate ओळ फक्त त्या बिल्ड लेयरच्या शेलवर परिणाम करते, त्यामुळे रनटाइमला कंटेनर सिस्टम इंटरप्रिटरवर सुरू होतो आणि ModuleNotFoundError त्रुटी दर्शवतो. ENV PATH="/opt/venv/bin:$PATH" सेट करा किंवा CMD ला /opt/venv/bin/gunicorn हा पूर्ण पाथ (absolute path) द्या. हा सिस्टमडीमधील त्रुटीसारखाच बग आहे, फक्त तो वेगळ्या फाईलमध्ये आहे.

थोडक्यात, कंटेनर इंटरप्रिटरच्या प्रश्नाची जागा घेतो, कारण इमेज इंटरप्रिटर आणि त्याखालील सर्व गोष्टींना पिन (pin) करते. तो पिनिंगच्या प्रश्नाची जागा घेत नाही. अनपिन केलेल्या requirements.txt पासून तयार केलेली इमेज पुढच्या महिन्यात वेगळ्या व्हर्जनवर रिझॉल्व्ह होऊ शकते. याचा अर्थ इमेज टॅग रिप्रोड्युसिबल (reproducible) असला तरी, ज्या बिल्डने तो तयार केला तो तसा नसतो. uv.lock सारखी लॉकफाईल किंवा पूर्णपणे पिन केलेली requirements फाईल ही दरी भरून काढते, मग कंटेनर असो वा नसो. आणि जेव्हा एखादे ॲप्लिकेशन सिस्टमडी अंतर्गत एका VPS वर चालते, तेव्हा कंटेनर हा निर्णय प्रामुख्याने Dockerfile मध्ये हलवतो, कारण सिस्टमडी आधीच अयशस्वी झालेली प्रोसेस रीस्टार्ट करते आणि तिचे आउटपुट जर्नलमध्ये कॅप्चर करते. VPS वर Docker चालवणे तेव्हाच फायदेशीर ठरते जेव्हा तुम्हाला तयार केलेली इमेजच डिप्लॉय करायची असते.

FAQ

मी --break-system-packages वापरून pip install करू शकतो का?

ज्या सर्व्हरला तुम्हाला सतत चालू ठेवायचे आहे, त्यावर असे करू नका. हे फ्लॅग स्वतःचे नाव सार्थ ठरवते: ते सुरक्षेचे कवच काढून टाकते आणि pip थेट /usr/local/lib/python3.12/dist-packages मध्ये लिहितो, जे sys.path वरील apt डिरेक्टरीच्या आधी येते. यामुळे तुमची आवृत्ती /usr/bin/python3 अंतर्गत चालणाऱ्या प्रत्येक सिस्टम स्क्रिप्टसाठी मूळ आवृत्तीला ओव्हरराईड (shadow) करते. apt ला अजूनही वाटते की त्यांचीच आवृत्ती इन्स्टॉल आहे, त्यामुळे काहीतरी बिघडल्याशिवाय या संघर्षाची (conflict) कोणालाही जाणीव होत नाही. कंटेनर इमेजमध्ये, जिथे तुम्ही प्रत्येक वेळी शून्यापासून बिल्ड करता, तिथे हे नुकसान फक्त त्या इमेजपुरते मर्यादित राहते, त्यामुळे तिथे हे स्वीकारार्ह असू शकते. मात्र, तुम्ही मेंटेन करत असलेल्या मशीनवर नेहमी venv तयार करा. हे फक्त एका कमांडचे काम आहे.

सर्व्हरवर व्हर्च्युअल एन्व्हायरनमेंट (virtual environment) कुठे असावे?

ते ॲप्लिकेशनच्या स्वतःच्या डिरेक्टरीमध्ये /srv/myapp/.venv म्हणून असावे. याची मालकी एका deploy युजरकडे असावी आणि सर्व्हिस अकाउंटकडे फक्त वाचण्याचे (read) आणि एक्झिक्युट करण्याचे अधिकार असावेत. प्रत्येक ॲप्लिकेशनसाठी एक स्वतंत्र venv ठेवा, कारण सामायिक (shared) venv वापरल्यास एका ॲप्लिकेशनचे अपग्रेड दुसऱ्याला बिघडवू शकते. venv तयार केल्यानंतर ती हलवू किंवा कॉपी करू नका: त्याच्या bin/ डिरेक्टरीमधील प्रत्येक स्क्रिप्टमध्ये तो पूर्ण पाथ (absolute path) त्याच्या shebang ओळीत लिहिलेला असतो. त्यामुळे venv हलवल्यास ती bad interpreter: No such file or directory एरर देऊन बंद पडते. त्याऐवजी ती डिलीट करा आणि requirements.txt वापरून पुन्हा तयार करा.

माझी systemd सर्व्हिस ModuleNotFoundError मुळे का फेल होते?

कारण युनिट अशा इंटरप्रिटरला एक्झिक्युट करत आहे जो venv चा नाही. systemctl cat myapp रन करा आणि ExecStart वाचा. त्यामध्ये /srv/myapp/.venv/bin/python चा किंवा त्याच bin/ डिरेक्टरीमधील एखाद्या कन्सोल स्क्रिप्टचा पूर्ण पाथ (absolute path) असणे आवश्यक आहे. युनिट फाईलमध्ये activate सोर्स करणे काम करत नाही, कारण ExecStart हे शेल नाही आणि systemd status=203/EXEC सह Failed to locate executable source रिपोर्ट करते. Environment=PATH=/srv/myapp/.venv/bin:/usr/local/bin:/usr/bin:/bin जोडा, जेणेकरून तुमच्या कोडद्वारे सुरू होणाऱ्या कोणत्याही सब-प्रोसेसला venv ची टूल्स सापडतील.

मी venv आणि pip ऐवजी uv वापरावे का?

जेव्हा तुम्हाला लॉकफाईल हवी असते, इन्स्टॉलेशनचा वेळ खूप जास्त वाटतो, किंवा तुमच्या डिस्ट्रिब्युशनमध्ये उपलब्ध नसलेली Python आवृत्ती हवी असते, तेव्हा uv वापरा. ते एक सामान्य venv तयार करते, त्यामुळे systemd युनिट आणि फाईल लेआउटमध्ये कोणताही बदल होत नाही आणि uv sync --frozen लॉकफाईलमध्ये नोंदवल्याप्रमाणेच सर्व काही इन्स्टॉल करते. जर एखादे ॲप्लिकेशन git वरून पिन केलेल्या requirements.txt सह डिप्लॉय होत असेल आणि इन्स्टॉलेशन काही सेकंदात पूर्ण होत असेल, तर python3 -m venv पुरेसे आहे आणि सर्व्हरवर अपडेट ठेवण्यासाठी एक बायनरी कमी असेल.

#python#venv#pipx#uv#deployment