Chạy Ollama rootless bằng Podman trên VPS
Hướng dẫn chạy Ollama bằng Podman rootless trên VPS: user riêng, lingering, Quadlet tự chạy sau reboot, SELinux label và đóng port 11434.
Chạy Ollama trong Podman rootless trên VPS
Để chạy Ollama trong Podman rootless trên máy chủ, phải đáp ứng 5 điều mà một hướng dẫn dành cho desktop có thể bỏ qua. Một user không có đặc quyền riêng sẽ sở hữu container. Phải bật lingering cho user đó để container tiếp tục chạy sau khi bạn đăng xuất. File Quadlet giao container cho systemd để container tự khởi động lại sau reboot. Thư mục chứa model phải có SELinux label trên các distribution thực thi SELinux. API chỉ lắng nghe trên loopback, và bạn truy cập API thông qua SSH (secure shell) tunnel.
Ollama là server dành cho các large language model (LLM). Ollama lưu model weights trên disk, nạp chúng vào memory và xử lý HTTP request trên port 11434. Ollama không có login, API key hoặc user account, nên network là lớp kiểm soát truy cập duy nhất bạn có. Podman chạy container không cần daemon và không cần root, vì vậy mọi tiến trình thoát được khỏi container ban đầu chỉ chạy dưới một user không có đặc quyền. Nếu muốn xem phần so sánh runtime trước, hãy đọc Podman và Docker khác nhau thế nào trên VPS. Nếu muốn bỏ qua container hoàn toàn, cài đặt Ollama trực tiếp trên VPS là cách ngắn hơn.
SSD Nodes cung cấp Fedora trong danh sách image, và Fedora mặc định có sẵn cả Podman lẫn SELinux (security-enhanced Linux). Mọi command bên dưới đều chạy được trên bất kỳ distribution nào có Podman 5 trở lên.
Vì sao phiên bản dành cho laptop cần thay đổi trên server
Fedora Magazine đã đăng một hướng dẫn rõ ràng về stack này vào ngày 5 August 2026: Chạy Ollama cục bộ với Podman trên Fedora Linux, do Yazan Monshed viết. Đây là tài liệu phù hợp để làm quen với các công cụ trong giờ đầu tiên. Tuy nhiên, tài liệu nhắm đến laptop, và 4 lựa chọn trong đó hoạt động khác trên máy có địa chỉ IP public.
- Tài liệu khởi động container bằng
podman run -dđơn giản. Container được khởi động thủ công sẽ không tự chạy lại sau reboot, vì chưa có cấu hình nào yêu cầu khởi động nó. - Tài liệu dùng tag thay đổi theo thời gian
ollama/ollama. Trên laptop, bạn sẽ nhận ra ngay ngày hành vi thay đổi. Trên server, dấu hiệu đầu tiên có thể là một script đột nhiên ngừng hoạt động qua đêm. - Tài liệu publish bằng
-p 11434:11434, bind trên mọi interface. Sau router gia đình, dịch vụ này không thể truy cập từ Internet. Trên VPS, đây trở thành một inference API public không có password. - Tài liệu chạy bằng user login của bạn. Trên server, account sở hữu container không nên sở hữu thêm bất kỳ thứ gì khác, để một lần break-out chỉ dẫn đến một home directory trống.
Không lựa chọn nào trong số đó là sai với máy mà tài liệu hướng đến. Mỗi lựa chọn chỉ là một quyết định cần xem xét lại khi máy có thể truy cập từ mọi nơi và không có ai ngồi trực tiếp trước máy.
Tạo user không có đặc quyền và kiểm tra subuid
Rootless Podman ánh xạ user ID (UID) nội bộ của container vào một dải ID chưa được sử dụng trên host. Dải này được khai báo trong /etc/subuid và /etc/subgid. Nếu không có dải này, rootless container hoàn toàn không thể khởi động.
sudo dnf install -y podman # or: sudo apt install -y podman
sudo useradd --create-home --shell /bin/bash --comment "Ollama container owner" ollama
sudo passwd --lock ollama
grep ollama /etc/subuid /etc/subgidgrep phải in ra 2 dòng, mỗi file 1 dòng. Mỗi dòng phải chỉ rõ một dải gồm 65536 ID:
/etc/subuid:ollama:100000:65536
/etc/subgid:ollama:100000:65536Số bắt đầu của bạn sẽ khác, và điều đó không sao. Nếu grep không in gì, useradd chưa cấp phát dải ID. Khi đó, lệnh podman đầu tiên chạy với user này sẽ fail như sau:
Error: cannot find UID/GID for user ollama: no subuid ranges found for user "ollama" in /etc/subuidCấp phát một dải mà không user nào khác đang sử dụng, sau đó báo cho Podman biết mapping cũ đã lỗi thời:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 ollama
sudo -iu ollama podman system migrateKhóa password để không ai đăng nhập trực tiếp bằng ollama. Bạn truy cập account này từ admin user bằng sudo -iu ollama.
Bật lingering để service tiếp tục chạy sau khi logout
Instance systemd của một user thường được khởi động khi đăng nhập và dừng khi logout, đồng thời /run/user/<uid> cũng bị xóa theo. Mọi rootless container do user đó sở hữu cũng dừng tại thời điểm đó. Lingering giữ cho instance của user tiếp tục chạy mà không cần session gắn với nó.
sudo loginctl enable-linger ollama
loginctl show-user ollama --property=LingerLệnh này phải in ra Linger=yes. Hãy bật lingering trước khi tạo unit, vì thư mục mà unit cần, /run/user/<uid>, chỉ tồn tại sau khi lingering được bật.
Còn một bước nữa thường không được tính đến. sudo -iu ollama cung cấp shell nhưng không cung cấp session bus, nên systemctl --user fail ngay lập tức:
Failed to connect to bus: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not definedsystemd tìm user bus tại $XDG_RUNTIME_DIR/bus, nhưng sudo -i không thiết lập biến đó. Hãy tự thiết lập biến này trong mọi shell quản trị dùng để quản lý service này:
sudo -iu ollama
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user statusNơi lưu các model blob và cách dự trù dung lượng đĩa
Ollama ghi các weight vào /root/.ollama/models bên trong container. Bind một thư mục trong home của user vào đường dẫn đó để các file được lưu tại vị trí có thể đo dung lượng: /home/ollama/ollama-data/models. Các blob được lưu trong models/blobs dưới dạng file content-addressed, còn models/manifests chứa index nhỏ dùng để định danh chúng. Nếu dùng named volume như bài viết trên Fedora Magazine, cùng cây thư mục đó sẽ nằm dưới /home/ollama/.local/share/containers/storage/volumes/<volume>/_data.
Hãy dự trù dung lượng đĩa trước khi pull bất kỳ thứ gì. Kích thước download được công bố là mức tối thiểu cần có.
The data behind this chart
[
{
"label": "gemma3:4b",
"download_gb": 3.3
},
{
"label": "mistral:7b",
"download_gb": 4.4
},
{
"label": "qwen3:8b",
"download_gb": 5.2
},
{
"label": "gemma3:12b",
"download_gb": 8.1
},
{
"label": "qwen3:14b",
"download_gb": 9.3
},
{
"label": "gemma3:27b",
"download_gb": 17
},
{
"label": "qwen3:30b",
"download_gb": 19
}
]Tất cả 7 dòng đều là số liệu được công bố trên ollama.com/library, không phải kích thước đo trực tiếp trên đĩa. Tag nhỏ nhất ở đây, gemma3:4b, cần download 3.3 GB. Tag lớn nhất, qwen3:30b, cần download 19 GB. Bản thân container image còn chiếm thêm dung lượng trong storage riêng của Podman, vì vậy hãy kiểm tra cả hai con số bằng podman system df và df -h /home. Khi được load, một model cũng cần lượng RAM xấp xỉ kích thước file, cộng thêm dung lượng cho context window. Vì vậy, model 19 GB sẽ không chạy được trên VPS có 16 GB RAM.
Ghim tag image và dùng tên registry đầy đủ
sudo -iu ollama
export XDG_RUNTIME_DIR=/run/user/$(id -u)
mkdir -p ~/ollama-data ~/.config/containers/systemd
podman pull docker.io/ollama/ollama:0.32.9Dùng tag của một phiên bản đã phát hành, 0.32.9 tính đến tháng 8 năm 2026, không dùng latest. Tag được ghim bảo đảm lần restart lúc 04:00 vẫn dùng đúng binary bạn đã kiểm thử, nên mọi thay đổi về hành vi đều là thay đổi do bạn thực hiện. Docker Hub cũng phát hành các tag -rc và -rocm cho cùng các phiên bản đó; hãy chọn tag thông thường, trừ khi bạn có GPU AMD.
Ghi cả hostname của registry. Trên Fedora, short name trong systemd unit không có terminal để hiển thị prompt, nên unit sẽ fail với:
Error: short-name "ollama/ollama" did not resolve to an alias and no unqualified-search registries are definedTrước tiên pull image thủ công là tùy chọn nhưng hữu ích, vì cách này đưa thao tác download vài gigabyte ra ngoài thời gian timeout khi unit start.
Unit Quadlet tồn tại sau khi reboot
Quadlet là systemd generator của Podman. Bạn viết một file .container, systemd chuyển file đó thành một service khi boot, và không còn cần podman generate systemd nữa. Lưu file này tại /home/ollama/.config/containers/systemd/ollama.container, với owner là user ollama.
[Unit]
Description=Ollama API (rootless)
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/ollama/ollama:0.32.9
ContainerName=ollama
PublishPort=127.0.0.1:11434:11434
Volume=/home/ollama/ollama-data:/root/.ollama:Z
Environment=OLLAMA_KEEP_ALIVE=30m
Environment=OLLAMA_MAX_LOADED_MODELS=1
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.targetTên file quyết định tên service, vì vậy ollama.container sẽ trở thành ollama.service.
systemctl --user daemon-reload
systemctl --user start ollama.service
systemctl --user status ollama.servicestatus phải hiển thị active (running). Không chạy systemctl --user enable ollama.service. Unit này không tồn tại dưới dạng file trên disk, nên systemd từ chối:
Failed to enable unit: Unit file /run/user/1001/systemd/generator/ollama.service is transient or generated.Section [Install] đã thực hiện việc đó. Quadlet tự tạo link start-at-boot trong quá trình daemon-reload, nên lệnh này là bắt buộc. TimeoutStartSec=900 xử lý lần start đầu tiên khi vẫn phải pull image, vì 90 giây mặc định không đủ cho việc download 2 gigabyte và systemd sẽ kill tiến trình start với trạng thái failed. OLLAMA_KEEP_ALIVE=30m giữ model trong memory giữa các request thay vì unload model sau năm phút; các đánh đổi được trình bày trong giữ model Ollama trong memory. Nếu có thuật ngữ systemd nào ở đây còn mới, cách service và timer của systemd hoạt động trên VPS giải thích về chính các unit đó.
Vì sao thư mục model trả về lỗi permission denied dưới SELinux
Trên Fedora, RHEL, Rocky và AlmaLinux, SELinux mặc định ở chế độ enforcing. Tiến trình container chạy trong domain container_t, còn thư mục trong home của user được gắn nhãn user_home_t. Policy không cho phép domain này truy cập nhãn kia, nên Ollama không thể tạo cây model và container thoát. getenforce in ra Enforcing trên các hệ thống này, và denial được ghi lại:
sudo ausearch -m avc -ts recentBạn sẽ thấy một dòng nêu domain và nhãn target:
avc: denied { write } for pid=1842 comm="ollama" name="models" dev="vda1" ino=131077 scontext=system_u:system_r:container_t:s0:c214,c827 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=dir permlisted=0:Z ở cuối dòng Volume= là cách khắc phục. Lệnh này gắn lại nhãn cho thư mục trên host thành container_file_t và gán cho nó một category MCS (multi-category security) riêng mà chỉ container này mang. Chữ thường :z dùng shared label thay thế, phù hợp khi hai container cùng đọc một thư mục.
Cần lưu ý về :Z vì thao tác này có tính hủy dữ liệu và không hiện thông báo. Việc gắn lại nhãn được thực hiện đệ quy. Nếu trỏ lệnh vào /home/ollama, mọi file trong thư mục home đó sẽ bị gắn lại nhãn, làm hỏng quyền truy cập SSH key của user đó. Luôn cung cấp cho :Z một thư mục con riêng và không chứa dữ liệu khác. Named volume không cần bước này vì Podman gắn nhãn đúng khi tạo volume. Nếu cần hiểu tổng quan hơn, Kiến thức cơ bản về SELinux cho server giải thích các context và boolean. Ubuntu và Debian dùng AppArmor thay thế, :Z không làm gì trên các hệ thống đó, và để nguyên lệnh này trong unit cũng không gây ảnh hưởng.
Đóng cổng 11434 và truy cập API qua SSH
PublishPort=127.0.0.1:11434:11434 bind phía host vào loopback. Xác nhận điều này:
ss -ltnp | grep 11434
curl http://127.0.0.1:11434Output của ss phải hiển thị 127.0.0.1:11434. 0.0.0.0:11434 hoặc *:11434 nghĩa là cổng đang mở ra Internet, và curl phải trả lời Ollama is running.
Hãy xác định chính xác bạn đang bind ở phía nào. Địa chỉ trong PublishPort là địa chỉ của host. Bên trong container, Ollama vẫn phải listen trên tất cả interface; đây là giá trị mặc định của image. Đặt Environment=OLLAMA_HOST=127.0.0.1 sẽ bind Ollama vào loopback riêng của container. Khi đó, Podman chuyển tiếp traffic đã publish đến địa chỉ network của container, nên mọi request đều bị từ chối, kể cả request từ host.
Để mở cổng 11434 sẽ gây ra 2 vấn đề. Ollama không có authentication, nên bất kỳ ai truy cập được cổng này đều có thể liệt kê model của bạn qua /api/tags, chạy inference trên CPU và bandwidth allowance của bạn qua /api/generate, tải model mới vào disk và xóa các model hiện có. Thứ hai, HTTP thuần đến một cổng remote sẽ gửi prompt và completion dưới dạng cleartext, nên mọi máy trên đường truyền đều có thể đọc chúng. Cả 2 vấn đề đều biến mất nếu cổng không bao giờ rời khỏi máy.
Từ workstation, forward cổng qua SSH:
ssh -N -L 11434:127.0.0.1:11434 you@vps.example.comBây giờ http://127.0.0.1:11434 trên laptop của bạn chính là Ollama trên server, nằm bên trong lớp mã hóa của phiên SSH. Nếu laptop đã chạy Ollama, local bind sẽ fail với bind [127.0.0.1]:11434: Address already in use; hãy dùng -L 11435:127.0.0.1:11434 và trỏ client đến 11435.
Khi client trên browser cần truy cập, hãy đặt một reverse proxy có password ở phía trước. Một site block của Caddy chỉ có 4 dòng, và caddy hash-password sẽ in ra bcrypt hash mà Caddy yêu cầu:
ollama.example.com {
basic_auth {
you $2a$14$replace_with_the_generated_hash
}
reverse_proxy 127.0.0.1:11434
}Caddy tự lấy certificate qua TLS (transport layer security), nên traffic được mã hóa. Trước tiên hãy test client: nhiều tool giao tiếp với Ollama không có trường cho header Authorization và sẽ fail với basic auth khi chỉ nhận được 401 Unauthorized. SSH tunnel không gặp vấn đề này, nên đây là khuyến nghị mặc định trong tài liệu này.
Tải model và kiểm tra toàn bộ đường đi
podman exec -it ollama ollama pull gemma3:4b
curl -s http://127.0.0.1:11434/api/tags
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"gemma3:4b","prompt":"Reply with the single word: ready","stream":false}'
du -sh ~/ollama-data/models/api/tags trả về JSON liệt kê gemma3:4b. /api/generate trả về một JSON object có trường response sau một khoảng dừng để nạp weights từ disk. du phải báo một số gần với kích thước download được công bố. Sau đó, hãy xác minh phần mà toàn bộ hướng dẫn này tập trung vào:
sudo reboot
# reconnect, then:
sudo -iu ollama
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user is-active ollama.serviceactive nghĩa là lingering, section [Install] và daemon-reload đều đã hoạt động đúng. inactive nghĩa là một trong ba phần đang thiếu.
Các trường hợp lỗi và chuỗi bạn sẽ thấy
Container biến mất sau khi reboot. Trước tiên hãy kiểm tra loginctl show-user ollama --property=Linger, vì nếu không có Linger=yes thì instance systemd của người dùng sẽ không bao giờ khởi động lúc boot. Nếu cơ chế lingering đang bật, phần [Install] bị thiếu trong file .container, hoặc bạn đã sửa file nhưng chưa chạy systemctl --user daemon-reload.
Error: statfs /home/ollama/ollama-data: no such file or directory. Source của bind mount phải tồn tại trước khi container khởi động. Podman không tự tạo thư mục trên host cho bạn. Chạy mkdir -p ~/ollama-data dưới user ollama.
Khởi động thất bại sau 90 giây. journalctl --user -u ollama.service hiển thị Start operation timed out. Terminating. vì quá trình pull image vẫn đang chạy. Hãy pull thủ công hoặc giữ lại TimeoutStartSec=900.
Container khởi động rồi thoát. podman logs ollama và sudo ausearch -m avc -ts recent kết hợp với nhau cho biết lỗi có liên quan đến SELinux label hay không. AVC có nhắc đến container_t và user_home_t nghĩa là :Z đang bị thiếu.
Request từ host bị từ chối. curl: (7) Failed to connect to 127.0.0.1 port 11434: Connection refused với service active thường có nghĩa là OLLAMA_HOST được đặt thành địa chỉ loopback bên trong container. Xóa dòng đó.
Quá trình generation rất chậm hoặc container bị kill. Khi không có GPU, inference chạy trên CPU nên model lớn vốn đã chậm. Container bị dừng giữa request và có signal: killed trong log là dấu hiệu kernel out-of-memory killer đã hoạt động, vì vậy hãy chọn tag nhỏ hơn trong chart ở trên.
Cập nhật image đã pin
Pinning nghĩa là bạn chủ động thực hiện việc cập nhật, thay vì để nó tự xảy ra. Chỉnh sửa Image= trong ollama.container, sau đó reload và restart:
systemctl --user daemon-reload
systemctl --user restart ollama.service
podman exec ollama ollama --versionModel nằm trong bind mount, nên vẫn giữ nguyên khi image thay đổi. AutoUpdate=registry trong phần [Container] dành cho trường hợp chạy tag thay đổi theo thời gian. Nó không có tác dụng khi dùng cùng một tag phiên bản cố định, vì nội dung của tag đó không thay đổi. Hãy backup /home/ollama/ollama-data/models/manifests và file .container, không cần sao lưu các blob: chúng có dung lượng lớn và ollama pull sẽ fetch lại chúng trên máy mới.
FAQ
Vì sao container rootless Podman của tôi dừng khi tôi đăng xuất?
Instance systemd của người dùng và thư mục /run/user/<uid> của họ sẽ bị dỡ khi session cuối cùng của người dùng kết thúc, nên mọi container rootless cũng dừng theo. Chạy sudo loginctl enable-linger ollama và xác nhận loginctl show-user ollama --property=Linger in ra Linger=yes. Bật lingering trước khi tạo unit Quadlet, vì runtime directory mà unit cần chỉ tồn tại khi lingering đã được bật.
Tôi có cần SELinux label cho thư mục model Ollama không?
Trên Fedora, RHEL, Rocky và AlmaLinux, có, nếu bạn bind mount một thư mục trên host. Container chạy trong domain container_t, còn thư mục trong home folder được gán label user_home_t, nên thao tác ghi bị từ chối và Ollama thoát. Thêm :Z vào dòng Volume= và cấp cho nó một subdirectory riêng, vì việc relabel sẽ đệ quy; trỏ :Z vào toàn bộ home directory sẽ làm hỏng quyền truy cập SSH key của người dùng đó. Named volume được Podman gán label đúng và không cần cấu hình thêm.
Model Ollama cần bao nhiêu dung lượng đĩa?
Bắt đầu từ dung lượng download được công bố trên ollama.com/library, dao động từ 3.3 GB cho gemma3:4b đến 19 GB cho qwen3:30b. Cộng thêm dung lượng image Podman, rồi chừa dung lượng dự phòng, vì model thứ hai không thay thế model thứ nhất trên đĩa. Kiểm tra df -h /home trước khi pull và du -sh ~/ollama-data/models sau đó. Lập kế hoạch RAM tương tự: khi được load, model cần lượng memory xấp xỉ kích thước file, cộng với context window.
Có an toàn không nếu expose port 11434 trên VPS?
Không. Ollama không có bất kỳ cơ chế authentication nào, nên bất kỳ ai truy cập được port này đều có thể liệt kê model, xóa model, pull model mới vào đĩa của bạn và chạy inference bằng CPU cùng hạn mức bandwidth của bạn. HTTP thuần qua Internet cũng gửi mọi prompt và completion dưới dạng cleartext. Bind phía host vào 127.0.0.1 bằng PublishPort=127.0.0.1:11434:11434, xác nhận bằng ss -ltnp | grep 11434, rồi truy cập qua SSH tunnel hoặc reverse proxy có yêu cầu password.