SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor

VPS-এ AI agent-এর জন্য headless browser চালানোর নিয়ম

VPS-এ Headless Chromium-এ /dev/shm ছোট হলে, sandbox flag না থাকলে, font অনুপস্থিত হলে বা process leak হলে সমস্যা হয়। আগে সীমা ঠিক করুন।

যা আপনি চালাচ্ছেন

একটি VPS-এ headless browser হলো কোনো window ছাড়া চলা Chromium, যা কোনো ব্যক্তি নয়, আপনার code দ্বারা নিয়ন্ত্রিত হয়। সার্ভারে এটি একটি দীর্ঘসময় চলমান process tree, যার সঙ্গে আপনার agent একটি local socket-এর মাধ্যমে যোগাযোগ করে। এটি install করতে একটি command-ই যথেষ্ট। আসল কাজ শুরু হয় তার পরে। browser মেশিন থেকে কতটা resource নিতে পারবে, তার সীমা নির্ধারণ করতে হবে। পাশাপাশি এর control endpoint-কে public Internet থেকে দূরে রাখতে হবে।

এই guide ধরে নিচ্ছে যে tool নির্বাচন ইতিমধ্যে সম্পন্ন হয়েছে এবং এখন আপনাকে এটি পরিচালনা করতে হবে। আপনি যদি এখনও crawler ও extractor-এর মধ্যে তুলনা করে থাকেন, তাহলে self-hosted Firecrawl-এর বিকল্পগুলো দিয়ে শুরু করে পরে এখানে ফিরে আসুন। নিচের সব উদাহরণ Playwright-এর Chromium ব্যবহার করে, কারণ Playwright নিজস্ব browser build এবং নিজস্ব dependency installer সরবরাহ করে। তাই একই command একটি bare Ubuntu VPS এবং একটি container—উভয় পরিবেশেই কাজ করে। Version-গুলো August 2026 অনুযায়ী বর্তমান।

নির্ভরতার অনুমান না করে Chromium ইনস্টল করুন

npm i -D playwright@1.62.0
npx playwright install --with-deps chromium

--with-deps প্রয়োজনীয় shared library ও font-এর জন্য apt চালায় এবং সেই পর্যায়ে root access চায়। Browser build নিজে command চালানো user-এর জন্য ~/.cache/ms-playwright-এ download হয়। Server-এ এটি গুরুত্বপূর্ণ, কারণ service user সাধারণত আপনি যে user হিসেবে লগ ইন করেন সেই user নয়। sudo npx playwright install-deps chromium ব্যবহার করে admin হিসেবে একবার system package ইনস্টল করুন। এরপর install command এবং service unit—উভয় জায়গায় PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers সেট করুন, যাতে একই copy share করা যায়। কোনো service তার browser দেখতে না পেলে launch-এর সময় যে path-এ search করেছে, সেই path উল্লেখ করে error message দেখায়।

Playwright version নির্দিষ্ট করে দিন। প্রতিটি release একটি নির্দিষ্ট browser build-এর সঙ্গে যুক্ত। তাই version নির্দিষ্ট না থাকা npm update চলমান service-এর browser পরিবর্তন করে দিতে পারে। August 2026 অনুযায়ী Playwright 1.62 current।

Chromium-এর দুটি build আছে। এগুলো একই program নয়। Default download হলো headless shell। এটি ছোট binary এবং শুধু headless mode-এ চলে। npx playwright install --with-deps --only-shell শুধু এটিই ইনস্টল করে। chromium channel ব্যবহার করলে full browser পাওয়া যায়। Playwright-এর browser documentation-এ এটিকে বলা হয়েছে “আসল Chrome browser, তাই এটি আরও authentic, reliable এবং আরও বেশি feature দেয়”। Bulk fetching-এর জন্য shell ব্যবহার করুন। কোনো site ভিন্নভাবে আচরণ করলে এবং কারণটি জানতে হলে full browser ব্যবহার করুন।

কনটেইনারে headless browser কেন crash করে

Docker প্রতিটি কনটেইনারকে 64 MB /dev/shm দেয়। Docker-এর documentation-এ স্পষ্টভাবে বলা আছে: "আপনি size সম্পূর্ণভাবে বাদ দিলে system 64m ব্যবহার করে"। Chromium তার process-গুলোর মধ্যে rendered content এই shared memory area-এর মাধ্যমে আদান-প্রদান করে। তাই একটি ভারী page এটি পূর্ণ করে ফেলতে পারে। এরপর renderer বন্ধ হয়ে যায় এবং আপনার client crashed target-এর report দেয়, যদিও একই page আপনার laptop-এ ঠিকমতো কাজ করে। কোনো পরিবর্তন করার আগে কনটেইনারের ভেতর থেকে size নিশ্চিত করুন।

df -h /dev/shm

এখানে দুটি কার্যকর সমাধান আছে। এগুলো বিকল্প, একসঙ্গে ব্যবহার করার জোড়া নয়। --ipc=host কনটেইনারকে host-এর IPC namespace-এ যুক্ত করে। ফলে এটি host-এর /dev/shm ব্যবহার করে, যার size সাধারণত RAM-এর অর্ধেক। Playwright-এর Docker guide এই পদ্ধতির সুপারিশ করে, কারণ এটি ব্যবহার না করলে "Chromium out of memory হয়ে crash করতে পারে"। এর বিনিময়ে কনটেইনার এবং host-এর মধ্যে IPC isolation আর থাকে না। --shm-size=1g private namespace বজায় রাখে এবং mount-এর size শুধু বাড়িয়ে দেয়।

docker run --rm -it --init --ipc=host --user pwuser mcr.microsoft.com/playwright:v1.62.0-noble /bin/bash

--disable-dev-shm-usage flag-টি অধিকাংশ search result-এ পাওয়া যায়। তবে এটি ভিন্ন কাজ করে: এটি ওই file-গুলোকে /dev/shm থেকে একটি temporary directory-তে সরিয়ে দেয়। /tmp যদি disk-এ থাকে, তাহলে crash-এর বদলে ধীর rendering এবং disk write পাবেন। /tmp যদি tmpfs হয়, তাহলে data কোনো size limit ছাড়াই আবার RAM-এ থাকবে। এর ফলে browser একটি ছোট VPS-এর RAM দ্রুত শেষ করে ফেলতে পারে। পরিবর্তে /dev/shm-এর size যথাযথভাবে নির্ধারণ করুন।

--no-sandbox ব্যবহারের প্রকৃত খরচ

Chromium প্রতিটি renderer-কে Linux user namespace-নির্ভর একটি sandbox-এর মধ্যে আলাদা করে রাখে। এই sandbox-ই একটি hostile page এবং আপনার server-এর মধ্যে সীমানা তৈরি করে। এটি চালু হতে না পারলে Chromium চলতে অস্বীকার করে, এবং log-এ এ ধরনের একটি line দেখা যায়:

Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted

সাধারণ পরামর্শ হলো --no-sandbox। Chromium-এর নিজস্ব security documentation-এ এই ব্যবহারের মূল্য স্পষ্টভাবে বলা হয়েছে: flag-টি “Chromium-এর critical security feature নিষ্ক্রিয় করে এবং open web ব্রাউজ করার সময় কখনো ব্যবহার করা উচিত নয়”। Link অনুসরণকারী একটি agent সংজ্ঞা অনুযায়ী open web ব্রাউজ করছে। প্রকৃত কারণ খুঁজে বের করুন।

প্রায় সব ক্ষেত্রে দুটি কারণের একটি দায়ী। Browser-টি root হিসেবে চালালে sandbox নিষ্ক্রিয় হয়, কারণ root ইতিমধ্যে থাকা privilege কমাতে পারে না। এই কারণেই Playwright-এর image-এ pwuser নামে একটি সাধারণ user থাকে। Ubuntu 24.04 এবং পরবর্তী সংস্করণে AppArmor unprivileged user namespace সীমিত করে। এমন কোনো path-এ থাকা Chromium binary, যার জন্য কোনো shipped profile প্রযোজ্য নয়, access denied পায়। ~/.cache/ms-playwright-এর অধীনে Playwright-এর download ঠিক এমন একটি path। দুটিই পরীক্ষা করুন:

id -u
sysctl kernel.apparmor_restrict_unprivileged_userns
sudo dmesg | grep -i userns_create

sysctl-এর একটি 1 এবং apparmor="DENIED" operation="userns_create"-সম্বলিত একটি kernel line দ্বিতীয় কারণটি নিশ্চিত করে। /etc/apparmor.d/pw-chromium-এ শুধু ওই binary-টিকে অনুমতি দিন। এতে server-এর অন্য সব কিছুর জন্য restriction কার্যকর থাকে:

abi <abi/4.0>,
include <tunables/global>

profile pw-chromium /home/*/.cache/ms-playwright/*/chrome-linux/{chrome,headless_shell} flags=(unconfined) {
  userns,
}

sudo apparmor_parser -r /etc/apparmor.d/pw-chromium দিয়ে এটি load করুন। path-এ browser revision থাকে, তাই প্রতিটি Playwright upgrade-এর সময় এটি পরিবর্তিত হয়। উপরের glob-গুলো এই পরিবর্তনের পরও কার্যকর থাকে। একটি নির্দিষ্ট path ধরে লেখা profile নীরবে matching বন্ধ করে দেয়। ফলে আপাতদৃষ্টিতে সম্পর্কহীন কোনো update-এর পরে browser আবার ব্যর্থ হতে শুরু করে।

স্ক্রিনশট ফাঁকা আসে বা বাক্সে ভরে যায় কেন

ফাঁকা স্ক্রিনশট বা খালি আয়তক্ষেত্রে ভরা স্ক্রিনশট সাধারণত rendering bug-এর কারণে নয়; বেশিরভাগ সময় এটি font সমস্যার লক্ষণ। install-deps একটি কার্যকর base সেট সংগ্রহ করে: জাপানির জন্য fonts-liberation, fonts-freefont-ttf, fonts-noto-color-emoji, fonts-unifont, fonts-ipafont-gothic; চীনা ভাষার জন্য fonts-wqy-zenhei; থাই ভাষার জন্য fonts-tlwg-loma-otf। এই সেটে Noto CJK নেই। তাই Korean এবং আরও কয়েকটি script-এর জন্য fontconfig যে font খুঁজে পায়, সেগুলো fallback হিসেবে ব্যবহৃত হয়। অনুমান না করে fontconfig-এর সাহায্যে পরীক্ষা করুন:

fc-match "sans-serif:lang=ko"
fc-match "sans-serif:lang=ar"
fc-list | wc -l

আপনার প্রয়োজনীয় কোনো ভাষা যদি unifont বা প্রকৃত glyph-বিহীন কোনো fallback-এ resolve হয়, তাহলে fonts-noto-core এবং fonts-noto-cjk install করুন। এরপর আবার পরীক্ষা চালান। Fontconfig তার ফলাফল cache করে রাখে। তাই font install করার পরে browser restart করুন। কোনো font ছাড়া stripped image startup-এ Fontconfig error: Cannot load default config file log করে এবং প্রতিটি page ফাঁকা render করে।

Locale এবং time zone font থেকে আলাদা। এগুলো page-এর শুধু চেহারা নয়, page-এ লেখা বিষয়বস্তুও পরিবর্তন করে। একটি container-এ সাধারণত LANG unset থাকে এবং TZ UTC-তে থাকে। ফলে site-গুলো English content পরিবেশন করে এবং UTC timestamp দেখায়। আপনার agent-ও এমন সময় জানায়, যা ওই দেশের কোনো ব্যক্তির দেখা সময়ের সঙ্গে মেলে না। এগুলো machine অনুযায়ী নয়, browser context অনুযায়ী সেট করুন। এতে একই browser বিভিন্ন region-এর জন্য task পরিচালনা করতে পারে।

const context = await browser.newContext({
  locale: 'en-GB',
  timezoneId: 'Europe/Paris',
});

ব্রাউজারের ফাঁস হওয়া process কেন সার্ভারকে swap ব্যবহার করতে বাধ্য করে

"zombie" নামে দুটি ভিন্ন সমস্যা বোঝানো হয়। প্রকৃত zombie হলো এমন একটি সমাপ্ত process, যার parent কখনও wait() কল করেনি। এটি শুধু একটি PID entry ধরে রাখে; অন্য কিছু নয়। তাই এটি memory ব্যবহার করে না। Container-এ browser যখন PID 1 হিসেবে চলে, তখন এই zombie process জমে। কারণ PID 1-এর কোনো default reaper থাকে না। Docker-এর --init flag এই সমস্যাটিই সমাধান করে। এটি একটি ছোট init চালায়, যা "signal forward করে এবং process reaping করে"। Compose-এ একই সেটিং হলো init: true

যে leak সত্যিই সার্ভারকে swap ব্যবহার করতে বাধ্য করে, সেটি আলাদা: সক্রিয় Chromium process, যেগুলো কোনো process বন্ধ করেনি। newContext() এবং close()-এর মাঝখানে কোনো task ব্যর্থ হলে, অথবা controlling script বন্ধ হয়ে browser tree orphaned হয়ে গেলে এটি ঘটে। সবচেয়ে খারাপ পরিস্থিতি হলো, প্রতিটি request-এর জন্য নতুন browser চালু করা। সংখ্যা গণনা করুন:

pgrep -c -f 'headless_shell|chrome'
ps -eo pid,ppid,rss,etime,comm --sort=-rss | head -20

Task চলাকালীন এই সংখ্যা idle অবস্থার মানে ফিরে আসা উচিত। এক দিন ধরে সংখ্যা বাড়তে থাকলে সমাধান launch flag-এ নয়, আপনার code-এ করতে হবে: finally block-এ context বন্ধ করুন, SIGTERM-এ browser বন্ধ করুন, এবং এক মাস ধরে একটি browser চালিয়ে না রেখে নির্দিষ্ট সংখ্যক task পর browser recycle করুন। systemd-এর অধীনে stop বা restart করলে unit-এর cgroup-এর সব process বন্ধ হয়ে যায়। তাই sudo systemctl restart browser.service একটি নির্ভরযোগ্য reset পদ্ধতি। Terminal multiplexer-এর ভিতরে হাতে চালু করা browser-এর ক্ষেত্রে এমন নিশ্চয়তা থাকে না। তার orphaned process session শেষ হওয়ার পরও চলতে থাকে।

একটি browser context-এর কত RAM প্রয়োজন

প্রশ্নটি নির্দিষ্টভাবে করুন, কারণ “একটি browser” বলতে একটি মাত্র process বোঝায় না। Chromium একটি browser process, একটি GPU process, কয়েকটি utility process এবং প্রতিটি site-এর জন্য একটি renderer process চালায়। Site isolation থাকলে cross-site iframe-এর জন্যও আলাদা renderer থাকে। একটি BrowserContext একই process tree-এর ভেতরে আলাদা cookie jar ও storage area তৈরি করে, তাই দ্বিতীয় context-এর খরচ কম। দ্বিতীয় page-এর খরচ কম নয়, কারণ এটি renderer process চালু করে। বিজ্ঞাপনে ভরা একটি page একাধিক renderer process-ও চালু করতে পারে।

তাই আপনার নিজস্ব workload-এ সম্পূর্ণ process tree-এর সর্বোচ্চ memory ব্যবহার মাপতে হবে। অন্য কারও blog-এর সংখ্যা এখানে কার্যত অপ্রাসঙ্গিক, কারণ আপনার agent যে page-গুলো খুলবে, তারাই ফল নির্ধারণ করে। আপনি যে machine ব্যবহার করবেন, সেই machine-এ এবং যে site-গুলো visit করবেন, সেগুলো দিয়ে মাপুন:

sudo systemd-run --unit=browser-probe -p MemoryMax=2G -p MemorySwapMax=0 -p WorkingDirectory=/srv/agent /usr/bin/node worker.js
systemctl status browser-probe

Ubuntu 24.04-এ ওই output-এর Memory: line-এ unit-এর বর্তমান এবং সর্বোচ্চ ব্যবহার দুটিই দেখা যায়। একবারে একটি page খুলে worker চালান এবং সর্বোচ্চ ব্যবহার লিখে রাখুন। এরপর দুটি page একসঙ্গে খুলে আবার মাপুন, যাতে দ্বিতীয় page-এর প্রকৃত অতিরিক্ত খরচ জানা যায়। এরপর concurrency নির্ধারণ করা arithmetic-এর বিষয়: মোট RAM থেকে machine-এর বাকি কাজের প্রয়োজনীয় RAM বাদ দিন, কয়েকশ MB headroom রাখুন, তারপর অবশিষ্ট RAM-কে প্রতি worker-এর মাপা সর্বোচ্চ ব্যবহারের অনুপাতে ভাগ করুন। এই workload-এর জন্য machine-এর আকার নির্ধারণ করতে একটি agent VPS-এর কত RAM এবং CPU প্রয়োজন দেখুন।

এই সীমা দুটি জায়গায় প্রয়োগ করুন। আপনার code-এ fixed worker pool বা semaphore ব্যবহার করুন, যাতে agent request-এর হঠাৎ বৃদ্ধি browser চালু না করে queue-তে অপেক্ষা করে। OS-এ cgroup limit ব্যবহার করুন, যাতে queue-তে থাকা bug machine-টিকেও অচল করে দিতে না পারে:

[Service]
MemoryMax=2G
MemorySwapMax=0
TasksMax=512
Restart=always

MemorySwapMax=0 প্রত্যাশার চেয়ে বেশি গুরুত্বপূর্ণ। এটি না থাকলে cgroup limit-এ পৌঁছানোর পর page-গুলোকে swap-এ পাঠায়। ফলে machine চালু থাকে, কিন্তু প্রতিটি request ধীর হয়ে যায়। পরিষ্কারভাবে failure হওয়ার তুলনায় এটি নির্ণয় করা কঠিন। MemorySwapMax=0 থাকলে kernel ওই cgroup-এর ভেতরের browser process tree বন্ধ করে, systemd unit-টি restart করে, এবং sshd সচল থাকে। Compose-এ একই নিয়ন্ত্রণগুলো হলো mem_limit, shm_size এবং init। এগুলো Docker Compose-এ memory limit নির্ধারণ অংশে ব্যাখ্যা করা হয়েছে।

ব্রাউজার endpoint-কে public Internet থেকে বিচ্ছিন্ন রাখুন

Playwright ব্রাউজারকে server হিসেবে চালাতে পারে এবং আপনার agent-কে একটি WebSocket URL দিতে পারে:

const { chromium } = require('playwright');
const server = await chromium.launchServer({ port: 3000 });
console.log(server.wsEndpoint());

এই endpoint-এ কোনো login নেই। Playwright-এর API documentation-এ বিষয়টি সরাসরি বলা হয়েছে: “wsPath সম্পর্কে জানা যেকোনো process বা web page (Playwright-এর মধ্যে চলমানগুলিও অন্তর্ভুক্ত) OS user-এর নিয়ন্ত্রণ নিতে পারে।” Default host হলো localhost, যা “শুধু loopback interface থেকে connection গ্রহণ করে”; documentation-এ আরও সতর্ক করা হয়েছে যে 0.0.0.0-এর মতো explicit address দিলে “listening port-এ পৌঁছাতে পারে এমন যেকোনো কিছুর কাছে browser RPC প্রকাশ পায়।” Chrome-এর নিজস্ব --remote-debugging-port আরও ঝুঁকিপূর্ণ। DevTools protocol-এ কোনো ধরনের authentication নেই এবং এটি সম্পূর্ণভাবে loopback-এ bind থাকার ওপর নির্ভর করে।

আপনি বাস্তবে কী publish করেছেন তা পরীক্ষা করুন। VPS ছাড়াও দ্বিতীয় একটি machine থেকে পরীক্ষা করুন:

ss -ltnp

0.0.0.0-এ bind করা browser port-এ কিছু পাওয়া গেলে সেটিকে security finding হিসেবে গণ্য করুন। মনে রাখবেন, অধিকাংশ provider তাদের control panel-এ আলাদা network firewall চালায়, যার নিয়ম সম্পর্কে আপনার ufw rules কিছুই জানে না। পরিবর্তে SSH tunnel বা private VPN ব্যবহার করে অন্য machine থেকে endpoint-এ পৌঁছান:

ssh -N -L 3000:127.0.0.1:3000 you@your-vps

এখানে ঝুঁকি শুধু কেউ browser ব্যবহারের সময় চুরি করার মধ্যে সীমাবদ্ধ নয়। আপনি নিয়ন্ত্রণ করতে পারেন এমন browser আপনার network-এর ভেতরে থাকা একটি request-forgery machine-এর মতো কাজ করে। যে ব্যক্তি ওই socket-এ পৌঁছাতে পারে, সে browser-কে http://127.0.0.1:8080, আপনার database admin page অথবা 169.254.169.254-এর cloud metadata address fetch করাতে পারে এবং page-এর response পড়তে পারে। আপনার firewall এটিকে VPS থেকেই আসা request হিসেবে দেখে, তাই সেটি অনুমোদিত হয়। এই control endpoint-কে ওই machine-এ shell access-এর সমতুল্য হিসেবে বিবেচনা করুন।

MCP server-গুলোর ক্ষেত্রেও একই বিষয় প্রযোজ্য। npx @playwright/mcp@latest --headless --port 8931 localhost-এ HTTP ব্যবহার করে service দেয়, আর --host 0.0.0.0 হলো সেই flag যা একটি local tool-কে public করে। প্রকল্পটির README-তে স্পষ্টভাবে বলা হয়েছে, Playwright MCP “security boundary নয়”। Port-টি loopback-এ রাখুন এবং একই tunnel ব্যবহার করে agent-কে এতে পৌঁছাতে দিন।

আপনার agent যে পৃষ্ঠাগুলো পড়ে, সেগুলো অবিশ্বস্ত ইনপুট

যে agent open web ব্রাউজ করে, সে অপরিচিত ব্যক্তিদের লেখা text এমন একটি model-এ পাঠায়, যেখানে আপনার instructions-ও থাকে। কোনো page সেই model-কে উদ্দেশ করে text বহন করতে পারে এবং task পরিত্যাগ করতে, কোনো tool call করতে বা কোনো URL-এ data post করতে বলতে পারে। Model উভয়কেই text হিসেবে পায়। তাই page-এর লেখা আর আপনার নির্দেশের মধ্যে পার্থক্য নির্ভরযোগ্যভাবে নির্ধারণ করার কোনো উপায় তার নেই। এমনভাবে setup করুন, যাতে hostile page-এর কাজে লাগানোর মতো সুযোগ খুব কম থাকে।

  • Browser-টি নিজস্ব OS user-এর অধীনে চালান। ওই user-এর environment-এ কোনো SSH keys বা cloud credentials রাখবেন না।
  • প্রতিটি task-এর জন্য নতুন context ব্যবহার করুন এবং Playwright MCP-এর সঙ্গে --isolated করুন, যাতে একটি site-এর session পরের page-এর কাছে উপলভ্য না থাকে।
  • যেখানে job এটি অনুমোদন করে, সেখানে origin allowlist রাখুন। Playwright MCP --allowed-origins এবং --blocked-origins-কে semicolon-separated lists হিসেবে গ্রহণ করে।
  • Mail পাঠানো বা অর্থ খরচ করার মতো state পরিবর্তনকারী কোনো action-এর আগে human step বাধ্যতামূলক করুন।

আরও নিরাপদ পদ্ধতি হলো পুরো browser এমন একটি machine-এ চালানো, যেটি ফেলে দিয়ে আবার rebuild করা যায়। এটি disposable VM-এ coding agents চালানোর একই যুক্তি। Agent-এর প্রকৃত কাজ যদি open-ended browsing নয়, বরং search হয়, তাহলে full browser-এর চেয়ে সীমিত tool নিরাপদ। নিজস্ব SearXNG-এর মাধ্যমে পরিচালিত একটি search skill hostile page কখনও load না করেই results ফেরত দেয়।

FAQ

Chromium একই VPS-এ সরাসরি ঠিকমতো চললেও Docker-এ কেন crash করে?

কারণ container ডিফল্টভাবে 64 MB /dev/shm পায়, কিন্তু host-এ এর আকার অনেক বেশি। Chromium rendered content এই shared memory area-এর মধ্য দিয়ে পাঠায়। তাই ভারী page এটি পূর্ণ করে ফেলে এবং renderer বন্ধ হয়ে যায়। নিশ্চিত হতে container-এর ভিতরে df -h /dev/shm চালান। এরপর হয় --ipc=host দিয়ে এটি চালান, যা host-এর shared memory ব্যবহার করে, অথবা --shm-size=1g ব্যবহার করুন, যা container-এর নিজস্ব shared memory-এর আকার বাড়ায়। --disable-dev-shm-usage শুধু সমস্যাটিকে /tmp-এ সরিয়ে দেয়।

VPS-এ অন্য কিছু না চললে --no-sandbox ব্যবহার করা কি নিরাপদ?

না। Sandbox ক্ষতিকর page-কে machine-এর বাকি অংশে পৌঁছাতে বাধা দেয়। Chromium-এর documentation-এ বলা হয়েছে, এই flag "disables critical security features of Chromium and should never be used when browsing the open web"। Link অনুসরণকারী agent open web browse করে। তাই মূল কারণ ঠিক করুন। Browser-কে root হিসেবে চালাবেন না। Ubuntu 24.04-এ browser binary path-এর জন্য userns, যুক্ত একটি AppArmor profile যোগ করুন, যাতে শুধু ওই program-এর জন্য unprivileged user namespace অনুমোদিত হয়।

ছোট VPS-এ কতগুলো browser চালাতে পারি?

কোনো সংখ্যা অনুলিপি না করে মাপুন। Chromium প্রতিটি site-এর জন্য একটি renderer process চালু করে। তাই উত্তরটি আপনি যে page-গুলো খুলছেন তার ওপর নির্ভর করে। MemoryMax সেট করে systemd-run-এর অধীনে একটি worker চালান। systemctl status-এর Memory: line থেকে peak মান পড়ুন। এরপর আপনার free RAM-কে সেই peak মান দিয়ে ভাগ করুন এবং অতিরিক্ত headroom রাখুন। ফলাফলটি দুবার কার্যকর করুন: আপনার code-এ একটি queue ব্যবহার করুন এবং unit file-এ একটি MemoryMax রাখুন। এতে request-এর চাপ বাড়লে machine swap করবে না; request অপেক্ষা করবে।

আমার agent কি অন্য machine থেকে browser-এর সঙ্গে সংযোগ করতে পারে?

হ্যাঁ, তবে port কখনো 0.0.0.0-এ bind করবেন না। Playwright server endpoint এবং Chrome DevTools port—দুটিই password ছাড়া যেকোনো reach করতে-পারা client-এর connection গ্রহণ করে। Listener-কে 127.0.0.1-এ রাখুন এবং SSH tunnel অথবা private VPN-এর মাধ্যমে connection বহন করুন। Server-এ ss -ltnp ব্যবহার করে যাচাই করুন এবং বাইরে থেকে একটি port check চালান। আপনার provider-এর আলাদা network firewall-ও পরীক্ষা করুন।

Page স্পষ্টভাবে load হলেও আমার screenshot blank কেন?

Font অনুপস্থিত। Page-এর script সমর্থন করে এমন font না থাকলে text খালি box হিসেবে render হয়, অথবা একেবারেই render হয় না। ফলে image কম থাকা page blank দেখায়। আপনি যে প্রতিটি language scrape করেন তার জন্য fc-match "sans-serif:lang=ko" চালান। ফল generic fallback হলে fonts-noto-core এবং fonts-noto-cjk install করুন। এরপর browser restart করুন, যাতে fontconfig তার cache পুনরায় load করে। কোনো font না থাকা container startup-এর সময় Fontconfig error: Cannot load default config file log করে।

#headless-browser#playwright#chromium#ai-agents#automation