Deploy bằng Docker
Chạy Dockerfile của bạn hoặc một image công khai có sẵn sau tên miền, với phát hành không gián đoạn có health check, rollback, đường dẫn lưu trữ cố định có sao lưu, log và cô lập container.
Khi ứng dụng không phù hợp với các runtime có sẵn (cần phiên bản ngôn ngữ khác, thư viện hệ thống riêng, image của nhà cung cấp), ZoPanel có thể chạy nó dưới dạng container. Hệ thống build Dockerfile trong repository của bạn, hoặc chạy một image công khai có sẵn, chỉ mở container trên 127.0.0.1 và đặt nginx, SSL, tường lửa của website phía trước. Với ứng dụng cài một chạm như n8n hay Nextcloud, hãy dùng Kho ứng dụng; cách đó không cần các quyền dưới đây.
Điều kiện cần
Phải đáp ứng đủ các điều kiện sau, nếu không lần deploy sẽ bị từ chối:
| Điều kiện | Ai thiết lập | Cách làm |
|---|---|---|
| Đã cài Docker | Quản trị viên | Kho ứng dụng → Cài Docker, hoặc Thành phần → Docker. |
| Máy chủ cho phép container tuỳ chỉnh | Root trên máy chủ | zopanel ctl feature enable custom-docker (xem bên dưới). |
| Tài khoản được chạy container riêng | Quản trị viên hoặc đại lý | Tài khoản thuộc quản trị viên toàn quyền, hoặc gói hosting của tài khoản bật Ứng dụng Docker riêng trong Gói hosting (mặc định tắt). Đại lý chỉ cấp được khi gói của chính đại lý có quyền này. |
| Gói có Git deploy | Quản trị viên hoặc đại lý | Git deploy & ứng dụng không bị tắt trong tính năng của gói. |
Bật container tuỳ chỉnh một lần, với quyền root:
sudo zopanel ctl feature enable custom-docker
Công tắc này chỉ có trên dòng lệnh của máy chủ, không có trên web panel, nên một phiên đăng nhập panel bị đánh cắp cũng không thể chạy container tuỳ ý.
Deploy Dockerfile từ repository
- Vào Website → Thêm website, chọn Git deploy, nhập Địa chỉ repository, Nhánh và, nếu
Dockerfilenằm trong thư mục con, Thư mục gốc. Bấm Tạo website. - Nếu repository chỉ có
Dockerfile(không cópackage.json,requirements.txt,go.mod…), nó được nhận diện là Dockerfile và build thành container. - Nếu hệ thống nhận diện ra framework khác, ghi chú "A Dockerfile was also found: switch the runtime to docker to build with it instead." sẽ hiện ra. Trong tab Deploy, tắt Tự nhận diện ở Build & chạy, đặt Runtime là Dockerfile (Loại chuyển thành Container (Dockerfile)) rồi bấm Lưu & deploy.
Build context là thư mục gốc: Dockerfile phải nằm ở đó, và cả nó lẫn .dockerignore phải là file thường, không phải symbolic link. ZoPanel chạy docker build --pull nên base image được làm mới ở mỗi lần build, và truyền PORT làm build argument (khai báo ARG PORT nếu cần). Ở chế độ này không dùng lệnh cài đặt, build hay chạy: hãy đặt chúng trong Dockerfile. Lần build chạy quá 45 phút sẽ bị dừng và lần deploy thất bại.
Deploy image có sẵn
- Tạo website (ví dụ Node / Proxy) hoặc mở website có sẵn, rồi vào tab Deploy.
- Ở Nguồn, chọn Docker image có sẵn.
- Nhập Image, ví dụ
nginx:1.27hoặcghcr.io/owner/app:1.2.0, rồi bấm Deploy.
Quy tắc với image:
- Chỉ image công khai (Docker Hub, GHCR hoặc registry công khai khác). Registry trên
localhosthoặc địa chỉ IP bị từ chối. - Tên viết thường, có thể kèm tag và digest (
app@sha256:…), tối đa 255 ký tự. - Nên ghi rõ tag. Mỗi lần deploy đều pull lại image, nên tag thay đổi như
latestcó thể khác nhau giữa các lần deploy.
ZoPanel biến image thành một bản release (một Dockerfile một dòng FROM image đó), nên nó có cùng health check, chuyển đổi không gián đoạn và rollback như Dockerfile tự build.
Cổng
Container phải lắng nghe trên một cổng HTTP. ZoPanel chọn cổng theo thứ tự:
- biến môi trường
PORT, nếu bạn đặt; EXPOSEđầu tiên trongDockerfile(với image có sẵn là cổng TCP đầu tiên image khai báo);8080.
Bên trong container, PORT được đặt bằng cổng đó. Bên ngoài, cổng được mở trên 127.0.0.1:<cổng nội bộ> (hiển thị là Cổng nội bộ trong tab Deploy) và nginx chuyển tên miền tới đó. Ngoài cổng này, không có gì của container truy cập được từ internet.
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci && npm run build
ENV PORT=3000
EXPOSE 3000
USER node
CMD ["node", "server.js"]
Ứng dụng bên trong nên lắng nghe 0.0.0.0:$PORT, không phải 127.0.0.1: trong container, 127.0.0.1 là chính container đó.
Biến môi trường
Thêm biến trong thẻ Biến môi trường rồi bấm Lưu & deploy lại. Tối đa 200 biến, giá trị một dòng. Biến được truyền vào container khi khởi động qua một file chỉ root đọc được, không bao giờ nằm trên dòng lệnh.
- Giá trị được truyền nguyên văn. Đừng bọc trong dấu nháy (Dán .env tự bỏ dấu nháy bao ngoài).
- Lúc build, một biến chỉ tới được các bước
RUNnếuDockerfilekhai báo nó bằngARG(xem bên dưới). Các biến còn lại chỉ có khi container chạy.
Biến lúc build
Khi Dockerfile khai báo ARG TEN_BIEN (hoặc ARG TEN_BIEN=mặc_định) và panel có biến TEN_BIEN, ZoPanel truyền nó cho docker build làm build argument. Log tác vụ liệt kê tên các biến được truyền (Build arguments from the environment variables: …), không bao giờ ghi giá trị.
FROM node:22-alpine AS build
ARG NEXT_PUBLIC_API_URL
WORKDIR /app
COPY . .
RUN npm ci && npm run build
- Chỉ những tên được khai báo mới được truyền, nên các biến khác (mật khẩu database, API key) không bao giờ bị build vào image.
- Giá trị build argument xem được trong lịch sử image:
docker historyhiển thị chúng ở mọi bướcRUNsauARG, và những gì một bước ghi ra từ chúng nằm lại trong layer đó. Đừng khai báo bí mật bằngARG; hãy đọc chúng từ biến môi trường lúc chạy. DùngARGcho cấu hình công khai như URL API công khai hay cờ tính năng. ARGđã khai báo mà panel không có biến tương ứng thì nhận giá trị mặc định trongDockerfile.- Các tên có thể thay đổi cách lệnh
dockercủa máy chủ chạy không bao giờ được truyền, ví dụPATH,HOME,LD_*,DOCKER_*,BUILDKIT_*,GO*,SSL_*và các biến proxy (HTTP_PROXY…). Log tác vụ liệt kê các tên bị bỏ qua.PORTluôn là cổng của container.
Health check và phát hành không gián đoạn
Mỗi bản release khởi động ở container thứ hai, trên cổng của slot rảnh. Bản đó được đưa lên khi Đường dẫn health check (mặc định /) trả về mã HTTP dưới 500 trong vòng 120 giây. Sau đó nginx chuyển sang container mới và container cũ được xoá sau vài giây. Nếu build lỗi, container thoát hoặc không đạt health check, 25 dòng log cuối của nó được ghi vào log tác vụ và container đang chạy vẫn tiếp tục phục vụ.
Rollback
Thẻ Các bản release giữ 5 bản gần nhất, mỗi bản giữ image đã build. Rollback chạy image của bản được chọn ở slot rảnh, chờ health check rồi chuyển, không build lại. Image của các bản cũ hơn bị xoá cùng bản release.
Log và điều khiển
- Log ứng dụng hiển thị 400 dòng log mới nhất của container kèm thời gian; bật Trực tiếp để theo dõi. Docker xoay vòng log container (3 file, mỗi file 10 MB).
- Thẻ trạng thái hiển thị tình trạng container, RAM và Cổng nội bộ. Các nút Khởi động lại, dừng và bật tác động lên container đang chạy.
- Log build (đầu ra của
docker build) của từng lần nằm ở Tác vụ.
Giới hạn và dữ liệu
| Giới hạn | Giá trị |
|---|---|
| RAM | 1024 MB mỗi container |
| CPU | 1 CPU |
| Số tiến trình | 512 |
| Khởi động lại | Tự khởi động lại, trừ khi đã bị dừng |
Trên máy chủ mà Docker dùng cgroup driver systemd (mặc định trên Ubuntu 22.04/24.04 và Debian 12/13), container còn chạy trong nhóm tài nguyên của tài khoản, nên giới hạn của gói cũng được áp dụng.
Quá trình build
docker build chạy trong Docker daemon, không chạy bằng user Linux của tài khoản. Với cgroup driver systemd, các container của những bước RUN khi build được đặt vào nhóm tài nguyên của tài khoản (--cgroup-parent), nên giới hạn CPU và RAM của gói áp dụng cho quá trình build giống như cho ứng dụng đang chạy: bước nào cần nhiều RAM hơn gói cho phép sẽ bị dừng (Killed trong log tác vụ). Cơ chế này hoạt động với BuildKit (thành phần buildx của Docker) lẫn với trình build cũ mà Docker dùng khi chưa cài buildx. Những phần không bị giới hạn của gói chi phối:
- tải base image, gửi build context và xuất image, do chính Docker daemon thực hiện;
- dung lượng đĩa của image và build cache (nằm trong
/var/lib/docker, không tính vào quota của tài khoản).
Mọi lần build đều bị dừng sau 45 phút.
Đường dẫn lưu trữ cố định
Hệ thống file của container được tạo lại ở mỗi lần deploy và rollback, nên mọi thứ ghi bên trong sẽ mất, trừ các đường dẫn lưu trữ cố định. Trong tab Deploy, ở Build & chạy, nhập chúng vào Đường dẫn lưu trữ cố định (trong container), cách nhau bằng dấu phẩy, rồi bấm Lưu & deploy:
/app/data, /app/uploads
- Mỗi đường dẫn là một thư mục tuyệt đối bên trong container, tối đa 10. Chỉ dùng chữ, số,
.,_,-và/; không có.., không có thư mục nằm trong một thư mục khác của danh sách. - Thư mục hệ thống bị từ chối:
/proc,/sys,/dev,/etc,/run,/boot,/bin,/sbin,/lib*,/usr/bin,/usr/sbin,/usr/lib*và mọi thứ bên trong, cùng chính các thư mục/,/usr,/usr/local,/usr/share,/usr/src,/var,/var/lib,/var/cache,/var/log,/home,/opt,/root,/tmp,/mntvà/media(thư mục con của chúng, như/var/lib/postgresql/datahay/usr/src/app/uploads, thì được). - Mỗi đường dẫn là một thư mục trên máy chủ trong
/var/lib/zopanel-apps/git-<user>-<domain>/, chỉ root và các container của website này truy cập được. Nó được gắn vào cùng đường dẫn ở cả hai slot release: trong lúc deploy, container cũ và mới cùng dùng nó vài giây, điều mà database dạng file như SQLite xử lý được. - Thư mục mới ban đầu trống và che mọi thứ image có ở đường dẫn đó, nên hãy dùng thư mục dữ liệu, không dùng thư mục chứa code. Thư mục thuộc về
USERcủa image khi đó là ID dạng số (USER 1000); khi image chạy bằng root, nó thuộc root trong container; khiUSERlà tên (USER node), mọi user trong container đều ghi được. Dữ liệu đã có giữ nguyên chủ sở hữu. - Giống dữ liệu ứng dụng trong Kho ứng dụng, nó không tính vào quota đĩa của tài khoản.
- Dữ liệu vẫn còn khi bạn đổi danh sách đường dẫn. Nó chỉ bị xoá cùng website khi bạn chọn xoá file của website, và khi xoá tài khoản.
- Khi chuyển tài khoản sang máy chủ khác, các đường dẫn được tạo lại ở máy chủ mới nhưng trống: hãy tự chép dữ liệu.
Các thư mục này sao lưu được giống dữ liệu của ứng dụng trong Kho ứng dụng: thẻ Sao lưu xuất hiện trong tab Deploy khi ứng dụng có đường dẫn lưu trữ cố định. Ai quản lý website cũng sao lưu ngay được; quản trị viên đặt lịch (hằng ngày hoặc hằng tuần, số bản giữ lại), khôi phục và xoá. Khi sao lưu, container tạm dừng trong lúc chép; khi khôi phục, container bị dừng, dữ liệu được thay rồi container chạy lại. Bản sao lưu nằm trong /var/backups/zopanel-apps/ trên máy chủ, tách riêng khỏi bản sao lưu tài khoản.
Với dữ liệu dùng chung với dịch vụ khác, database trên máy chủ khác hoặc object storage, ví dụ Lưu trữ S3 của ZoPanel đã gắn tên miền (truy cập qua HTTPS cổng 443), vẫn là nơi phù hợp hơn.
Cô lập
Container của khách chạy mã mà máy chủ không kiểm soát, nên ZoPanel giới hạn nó:
- Mạng: container không mở được kết nối tới địa chỉ private, shared, loopback hay link-local (kể cả dịch vụ metadata của cloud tại
169.254.169.254), cũng không kết nối được tới container khác. Trên chính máy chủ, container chỉ tới được các cổng công khai: web (80, 443), mail (25, 465, 587) và DNS. MySQL, PostgreSQL, Redis và panel của máy chủ không truy cập được từ container. Quá trình build image cũng chịu cùng quy tắc. - Quyền: mọi Linux capability bị bỏ, trừ
CHOWN,DAC_OVERRIDE,FOWNER,SETUID,SETGID,NET_BIND_SERVICEvàKILL; bậtno-new-privileges; áp dụng profile seccomp và AppArmor mặc định của Docker. - User namespace: root trong container tương ứng với một dải ID không có đặc quyền trên máy chủ.
- Không truy cập máy chủ: không có Docker socket, không gắn thư mục nào của máy chủ ngoài các đường dẫn lưu trữ cố định của chính website.
Nếu không áp dụng được các quy tắc này, ZoPanel không build và không khởi động container.
Xử lý sự cố
| Thông báo | Nguyên nhân và cách xử lý |
|---|---|
this account's package does not allow its own Docker apps (Dockerfile or image); ask the administrator |
Bật Ứng dụng Docker riêng trong gói của tài khoản, hoặc deploy từ tài khoản của quản trị viên toàn quyền. |
Dockerfile deployments are disabled; enable them on the server with: zopanel ctl feature enable custom-docker |
Chạy lệnh này với quyền root trên máy chủ. |
Docker is not installed (install it from the App Store page) |
Cài Docker từ Kho ứng dụng hoặc Thành phần. |
no Dockerfile in the build directory |
Dockerfile phải nằm trong Thư mục gốc (tên phân biệt hoa thường). |
docker build failed: … |
Một bước build bị lỗi; log tác vụ có đầu ra chi tiết. Các bước tải dữ liệu từ địa chỉ private bị chặn. Killed ở một bước nghĩa là nó cần nhiều RAM hơn gói của tài khoản cho phép. |
docker build did not finish within 45 minutes |
Làm build nhanh hơn (context nhỏ hơn nhờ .dockerignore, ít bước hơn), hoặc build image ở nơi khác rồi deploy dưới dạng image có sẵn. |
persistent path … is not allowed / must be an absolute path inside the container |
Dùng thư mục tuyệt đối của ứng dụng như /app/data, không dùng thư mục hệ thống. |
| Mất dữ liệu sau khi deploy | Dữ liệu được ghi ngoài các đường dẫn lưu trữ cố định. Thêm thư mục đó vào Đường dẫn lưu trữ cố định (trong container). |
| Ứng dụng không ghi được vào đường dẫn lưu trữ cố định | Thư mục được tạo khi image còn chạy bằng user khác, hoặc ứng dụng cần file mà image có sẵn ở đường dẫn đó (thư mục mới ban đầu trống). Dùng USER dạng số, hoặc chown thư mục trong entrypoint chạy bằng root (container giữ quyền CHOWN). |
cannot pull … (public images only): … |
Image hoặc tag không tồn tại, hoặc là image riêng tư. |
image must look like nginx:1.27 or ghcr.io/owner/app:v2 |
Dùng tên image viết thường. |
the container did not answer on port … within 120s (set PORT or EXPOSE to the port it listens on) |
Ứng dụng lắng nghe cổng khác hoặc lắng nghe 127.0.0.1 bên trong container. Đặt PORT, hoặc lắng nghe 0.0.0.0:$PORT. |
the container exited during startup |
Đọc log container trong log tác vụ: thường do thiếu biến hoặc lệnh kết thúc ngay. |
Docker runs without user-namespace isolation; restart the ZoPanel agent to enable it |
Chạy sudo systemctl restart zopanel-agent trên máy chủ. |
the network isolation for containers could not be set up (iptables); Docker apps are not started without it |
Không cài được quy tắc tường lửa. Kiểm tra iptables hoạt động và Docker đang chạy. |
| Ứng dụng không kết nối được database | Container không tới được database của chính máy chủ, kể cả qua IP công khai. Dùng database trên máy chủ khác, truy cập qua địa chỉ công khai. |