SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-13

Python venv, pipx নাকি uv: সার্ভারের জন্য কোনটি সেরা?

Ubuntu সার্ভারে pip install ব্যর্থ হলে externally-managed-environment এর সমাধান জানুন। আপনার কাজের ধরন অনুযায়ী venv, pipx বা uv ব্যবহারের সঠিক নিয়ম এবং systemd কনফিগারেশন শিখুন।

কেন নতুন 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)' দিয়ে এটি নিজে প্রিন্ট করে দেখুন এবং অর্ডারটি পড়ুন। ফলে pip যে কপিটি লিখেছে তা apt-এর ইনস্টল করা কপিটিকে আড়াল (shadow) করে ফেলে, যা ওই বক্সে /usr/bin/python3-এর অধীনে চলা প্রতিটি প্রোগ্রামের ক্ষেত্রে ঘটে, এমনকি ডিস্ট্রিবিউশনের নিজস্ব টুলগুলোর ক্ষেত্রেও। cloud-init সেই ইন্টারপ্রিটার থেকে requests, jinja2 এবং PyYAML ইমপোর্ট করে। pip দিয়ে এর কোনো একটি আপডেট করলে এবং বেমানান কোনো রিলিজ ইনস্টল হয়ে গেলে, আপনি কখনো স্পর্শ করেননি এমন কোনো কিছু পরবর্তী বুটের সময় ব্যর্থ হবে এবং এমন একটি প্যাকেজের নাম দেখাবে যা আপনি জানতেন না যে চেইনের ভেতরে আছে। apt তখনও তার নিজস্ব ভার্সন ইনস্টল করা আছে বলে রেকর্ড রাখে, তাই কোনো সতর্কতা পাওয়া যায় না এবং এটি ঠিক করার উপায় হলো sudo apt reinstall python3-requests

এর পরের নিয়মটি সংক্ষিপ্ত। সিস্টেম Python ডিস্ট্রিবিউশনের নিজস্ব। এতে কিছু ইনস্টল করবেন না, pip দিয়ে এর লাইব্রেরি আপডেট করবেন না এবং মেসেজটি সরানোর জন্য EXTERNALLY-MANAGED ফাইলটি ডিলিট করবেন না। /usr/bin/python3-কে শুধুমাত্র ভার্চুয়াল এনভায়রনমেন্ট তৈরির কাজ দিন।

venv বনাম pipx বনাম uv: সিদ্ধান্ত গ্রহণের নিয়ম

আপনি কী ইনস্টল করছেন তার ওপর ভিত্তি করে টুল নির্বাচন করুন, সম্প্রতি কোন টুলের কথা পড়েছেন তার ওপর নয়।

  • একটি অ্যাপ্লিকেশন যা আপনি সার্ভিস হিসেবে ডেপ্লয় এবং রান করেন, যেমন Django বা Flask প্রজেক্ট: সেই অ্যাপ্লিকেশনের ডিরেক্টরির ভেতরে একটি virtual environment (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 ব্যবহার করে দেখানো হয়েছে, তাই আপনার ভার্সন ভিন্ন হলে পাথ-এর ভেতরের ভার্সন নম্বরটি পরিবর্তন করে নিন।

প্রতিটি অ্যাপ্লিকেশনের জন্য venv তৈরি করা

Ubuntu মূল Python প্যাকেজ থেকে venv মডিউলটিকে আলাদা করে রাখে, তাই একটি minimal image-এ প্রথমবার চেষ্টা করলে সেটি ব্যর্থ হবে এবং ঠিক কী অনুপস্থিত তা একটি বার্তার মাধ্যমে জানিয়ে দেবে।

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/-এর প্রতিটি স্ক্রিপ্টে একটি absolute 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 unit এমন একটি পাথ পায় যা কখনোই পরিবর্তিত হয় না এবং একটি শেয়ারড ডিপেন্ডেন্সি আপগ্রেডের কারণে দুটি অ্যাপ্লিকেশন একে অপরের ক্ষতি করতে পারে না। আপনার ওয়েব সার্ভার সরাসরি ফাইল পাবলিশ করে এমন কোনো জায়গায় 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

সার্ভিসটি এখন তার ডিপেন্ডেন্সিগুলো ইমপোর্ট করতে পারে কিন্তু সেগুলো পরিবর্তন করতে পারে না। এর মানে হলো, ওয়েব অ্যাপ্লিকেশনে কোনো কোড এক্সিকিউশন বাগ থাকলে তা নীরবে ডিস্কে থাকা কোনো লাইব্রেরি প্রতিস্থাপন করতে পারবে না এবং রিস্টার্টের পরেও টিকে থাকতে পারবে না। মেশিনের অন্যান্য অংশের ক্ষেত্রেও একই যুক্তি প্রযোজ্য, যা least-privilege ব্যবহারকারী হিসেবে সার্ভিস চালানো অংশে আলোচনা করা হয়েছে।

কমান্ড-লাইন টুলের জন্য 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 দেখালে বুঝতে হবে আপনি এখনো লগ আউট করে পুনরায় লগ ইন করেননি। উবুন্টুর ডিফল্ট ~/.profile শুধুমাত্র তখনই ~/.local/bin যোগ করে যখন লগ ইন করার সময় সেই ডিরেক্টরিটির অস্তিত্ব থাকে। এই কারণেই নতুন অ্যাকাউন্টে প্রথমবার এই সমস্যাটি হয় এবং পরে আর হয় না।

pipx-কে কোনো লাইব্রেরি ইনস্টল করতে বললে এটি তা প্রত্যাখ্যান করে এবং নিচের বার্তাটি দেখায়:

No apps associated with package requests or its dependencies.

এটি টুলটির মাধ্যমে আপনাকে জানানো যে আপনি ভুল টুল ব্যবহার করছেন। লাইব্রেরিগুলো একটি অ্যাপ্লিকেশনের venv-এর মধ্যে থাকা উচিত।

সার্ভারের ক্ষেত্রে গুরুত্বপূর্ণ বিষয় হলো লোকেশন। সাধারণ pipx install কমান্ড সবকিছু একটি ইউজারের হোম ডিরেক্টরির অধীনে রাখে। myapp হিসেবে চলমান কোনো systemd ইউনিট এটি দেখতে পায় না, রুট ক্রন জব এটি দেখতে পায় না এবং 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 দিয়ে আপনার ভার্সন চেক করে নিন, কারণ উবুন্টু 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-এর একটি একক binary, যা 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 একটি স্বয়ংসম্পূর্ণ binary যার নিজস্ব কোনো Python dependency নেই, তাই এটিকে /usr/local/bin-এ কপি করা সার্ভারের সকল ব্যবহারকারীর সাথে শেয়ার করার একটি বৈধ উপায়।

একটি pyproject.toml যুক্ত প্রজেক্টের ক্ষেত্রে ওয়ার্কফ্লোটি চারটি কমান্ডের, যার মধ্যে কেবল শেষটি সার্ভারে চালানো হয়।

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

uv lock ফাইলটি uv.lock তৈরি করে, যা একটি ক্রস-প্ল্যাটফর্ম lockfile এবং এতে সুনির্দিষ্ট ভার্সনগুলো সংরক্ষিত থাকে; আপনি এটি আপনার কোডের সাথে commit করবেন। uv sync প্রজেক্টের রুটে .venv তৈরি করে যা lockfile-এর সাথে সামঞ্জস্যপূর্ণ। সার্ভারে, --frozen ফ্ল্যাগটি গুরুত্বপূর্ণ: ডকুমেন্টেশন অনুযায়ী এটি lockfile-এর ভার্সনগুলোকে সত্যের উৎস (source of truth) হিসেবে ব্যবহার করে, এটি যাচাই করার পরিবর্তে যে 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

এর আউটপুট একটি সাধারণ virtual environment। .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 ইন্টারপ্রেটারের দিকে নির্দেশ করুন

পাইথন ডেপ্লয়মেন্টের বেশিরভাগ সমস্যা এখানেই হয়, এবং এর কারণ হলো activate কী কাজ করে সে সম্পর্কে ভুল ধারণা।

bin/activate একটি শেল স্ক্রিপ্ট। এটি venv-এর bin ডিরেক্টরিকে PATH-এর শুরুতে যুক্ত করে, VIRTUAL_ENV সেট করে, পুরনো মানগুলো সংরক্ষণ করে যাতে deactivate সেগুলো পুনরুদ্ধার করতে পারে এবং আপনার প্রম্পট পরিবর্তন করে। এতে এমন কিছু নেই যা ইন্টারপ্রেটার নিজে পড়ে। অ্যাক্টিভেশন শুধুমাত্র একজন মানুষের জন্য সুবিধাজনক, যিনি প্রম্পটে python টাইপ করেন।

প্রকৃতপক্ষে কোন এনভায়রনমেন্টটি কাজ করবে তা নির্ভর করে আপনি কোন ইন্টারপ্রেটার ফাইলটি চালাচ্ছেন তার ওপর। যখন /srv/myapp/.venv/bin/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 বিল্ট-ইন নেই, && একটি আক্ষরিক আর্গুমেন্ট হিসেবে পাঠানো হয় এবং কোনো কিছুই এক্সপ্যান্ড হয় না।

এবং এই ইউনিটটি শুরু হয়েই বন্ধ হয়ে যায়:

[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 এবং আপনার প্রসেসের মাঝে একটি শেল যুক্ত করে যা কোনো বাড়তি সুবিধা দেয় না, অথচ একটি অ্যাবসোলিউট পাথ ব্যবহার করলেই বিষয়টি সমাধান হয়ে যায়:

[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 পায়, তাই পাইথন কোড যখন subprocess.run(["ffmpeg", ...]) কল করে, অথবা কোনো ম্যানেজমেন্ট কমান্ড যখন venv থেকে কনসোল স্ক্রিপ্ট চালায়, তখন তারা প্রয়োজনীয় ফাইল খুঁজে পায় না। venv-এর bin ডিরেক্টরিকে শুরুতে রাখা হলো activate-এর একমাত্র অংশ যা একটি সার্ভিস প্রকৃতপক্ষে ব্যবহার করে। systemctl show -p Environment myapp দিয়ে ইউনিটটি আসলে কী পেয়েছে তা যাচাই করুন।

একই নিয়ম শিডিউল করা কাজের ক্ষেত্রেও প্রযোজ্য। cron কাজগুলো /usr/bin:/bin-এর PATH নিয়ে চালায়, তাই 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 ব্যবহার করা হয়, কারণ এটি মাল্টি-স্টেজ বিল্ডকে সহজ করে তোলে। বিল্ডার স্টেজটি /opt/venv-এ ইনস্টল করে এবং রানটাইম স্টেজটি শুধুমাত্র সেই ডিরেক্টরিটিকে কপি করে নেয়, ফলে কম্পাইলারগুলো বাদ পড়ে যায়। activate সংক্রান্ত সমস্যাটি এর সাথেই থেকে যায়। একটি RUN source /opt/venv/bin/activate লাইন শুধুমাত্র সেই বিল্ড লেয়ারের শেলকে প্রভাবিত করে, তাই রানটাইমে কন্টেইনারটি সিস্টেম ইন্টারপ্রেটারে চালু হয় এবং ModuleNotFoundError ত্রুটি দেখায়। এক্ষেত্রে ENV PATH="/opt/venv/bin:$PATH" সেট করুন, অথবা CMD-কে /opt/venv/bin/gunicorn অ্যাবসোলিউট পাথ প্রদান করুন। এটি systemd-এর মতোই একটি বাগ, যা ভিন্ন একটি ফাইলে বিদ্যমান।

সুতরাং, কন্টেইনার ইন্টারপ্রেটার সংক্রান্ত প্রশ্নটিকে প্রতিস্থাপন করে, কারণ ইমেজটি ইন্টারপ্রেটার এবং এর অধীনস্থ সবকিছুকে পিন (pin) করে রাখে। এটি পিনিং সংক্রান্ত প্রশ্নটিকে প্রতিস্থাপন করে না। একটি আনপিনড requirements.txt থেকে তৈরি ইমেজ পরের মাসে ভিন্ন ভিন্ন ভার্সন রেজলভ করতে পারে, যার অর্থ হলো ইমেজ ট্যাগটি রিপ্রোডিউসিবল হলেও যে বিল্ড থেকে এটি তৈরি হয়েছে তা নয়। কন্টেইনার থাকুক বা না থাকুক, uv.lock-এর মতো একটি লকফাইল বা সম্পূর্ণ পিন করা রিকোয়ারমেন্ট ফাইলই এই ব্যবধান দূর করে। আর যখন একটি অ্যাপ্লিকেশন systemd-এর অধীনে একটি VPS-এ চলে, তখন কন্টেইনার মূলত এই একই সিদ্ধান্তকে একটি Dockerfile-এ স্থানান্তর করে, কারণ systemd ইতিমধ্যেই ব্যর্থ প্রসেস রিস্টার্ট করে এবং এর আউটপুট জার্নালে সংরক্ষণ করে। VPS-এ Docker চালানো তখনই কার্যকর হয় যখন আপনি বিল্ড করা ইমেজটিকেই সরাসরি ডেপ্লয় করতে চান।

FAQ

আমি কি --break-system-packages ফ্ল্যাগ দিয়ে pip install ব্যবহার করতে পারি?

যে সার্ভার আপনাকে সচল রাখতে হবে, সেখানে এটি করবেন না। এই ফ্ল্যাগটি তার নামের মতোই কাজ করে: এটি সুরক্ষাকবচ সরিয়ে ফেলে এবং pip সরাসরি /usr/local/lib/python3.12/dist-packages ডিরেক্টরিতে ফাইল লেখে, যা sys.path-এ apt ডিরেক্টরির আগে থাকে। ফলে /usr/bin/python3-এর অধীনে চলা প্রতিটি সিস্টেম স্ক্রিপ্টের জন্য আপনার ইনস্টল করা ভার্সনটি ডিস্ট্রিবিউশনের ভার্সনকে ছাপিয়ে যায়। এদিকে apt মনে করে তাদের নিজস্ব ভার্সনটিই ইনস্টল করা আছে, তাই কোনো কিছু ভেঙে না যাওয়া পর্যন্ত এই কনফ্লিক্ট ধরা পড়ে না। কন্টেইনার ইমেজের ভেতরে, যা আপনি প্রতিবার নতুন করে তৈরি করেন, সেখানে এই ক্ষতি কেবল সেই ইমেজেই সীমাবদ্ধ থাকে, তাই সেখানে এটি গ্রহণযোগ্য। কিন্তু যে মেশিন আপনি রক্ষণাবেক্ষণ করেন, সেখানে একটি venv তৈরি করুন। এটি মাত্র একটি কমান্ডের কাজ।

সার্ভারে ভার্চুয়াল এনভায়রনমেন্ট কোথায় রাখা উচিত?

অ্যাপ্লিকেশনের নিজস্ব ডিরেক্টরির ভেতরে /srv/myapp/.venv নামে এটি রাখুন। এর মালিকানা থাকবে একজন deploy ইউজারের কাছে এবং সার্ভিস অ্যাকাউন্টের কাছে কেবল পড়ার (read) ও চালানোর (execute) অনুমতি থাকবে। প্রতিটি অ্যাপ্লিকেশনের জন্য আলাদা venv রাখুন, কারণ শেয়ারড venv ব্যবহার করলে একটি অ্যাপ্লিকেশনের আপগ্রেড অন্যটিকে ভেঙে দিতে পারে। একটি venv তৈরি করার পর সেটিকে সরাবেন না বা কপি করবেন না: এর bin/ ডিরেক্টরির প্রতিটি স্ক্রিপ্টের shebang লাইনে সেই অ্যাবসোলিউট পাথ লেখা থাকে, তাই venv সরিয়ে ফেললে তা bad interpreter: No such file or directory এরর দিয়ে ব্যর্থ হয়। এর বদলে সেটি মুছে ফেলে requirements.txt থেকে নতুন করে তৈরি করুন।

কেন আমার systemd সার্ভিস ModuleNotFoundError দিয়ে ব্যর্থ হয়?

কারণ ইউনিটটি এমন একটি ইন্টারপ্রেটার চালাচ্ছে যা venv-এর নয়। systemctl cat myapp চালান এবং ExecStart পড়ুন। সেখানে অবশ্যই /srv/myapp/.venv/bin/python অথবা একই bin/ ডিরেক্টরির কোনো কনসোল স্ক্রিপ্টের অ্যাবসোলিউট পাথ থাকতে হবে। ইউনিট ফাইলে 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 ব্যবহার করা উচিত?

যখন আপনার একটি lockfile প্রয়োজন, ইনস্টলেশনের সময় অনেক বেশি লাগে, অথবা আপনার ডিস্ট্রিবিউশনে নেই এমন কোনো Python ভার্সন দরকার হয়, তখন uv ব্যবহার করুন। এটি একটি সাধারণ venv তৈরি করে, তাই systemd ইউনিট বা ফাইল লেআউটে কোনো পরিবর্তন করতে হয় না এবং uv sync --frozen ঠিক সেই প্যাকেজগুলোই ইনস্টল করে যা lockfile-এ রেকর্ড করা আছে। যদি একটি অ্যাপ্লিকেশন git থেকে একটি নির্দিষ্ট requirements.txt দিয়ে ডিপ্লয় করা হয় এবং ইনস্টলেশন কয়েক সেকেন্ডেই শেষ হয়ে যায়, তবে python3 -m venv ই যথেষ্ট। এতে সার্ভারে আপডেট রাখার মতো বাড়তি একটি বাইনারি কম থাকে।

#python#venv#pipx#uv#deployment