Đặt app ở đâu trên VPS? Cấu trúc thư mục chuẩn /opt/apps
Quy ước đặt mỗi app trong một thư mục con của /opt/apps trên VPS: chown thư mục cha một lần duy nhất, từ đó git clone, git pull và sửa .env đều không cần sudo.
Mục lục
- Tóm tắt thao tác nhanh
- Các bước thực hiện
- Bước 1: Tạo thư mục cha /opt/apps (chỉ 1 lần cho cả VPS)
- Bước 2: Clone app mới (từ giờ không cần sudo)
- Bước 3: Cấu trúc thư mục thu được (toàn cảnh từ gốc /)
- Câu hỏi thường gặp
- Vì sao chọn /opt thay vì ~/apps?
- ls ~ không thấy /opt/apps?
- Nhiều người cùng quản trị VPS (tùy chọn)
- Vị trí các thành phần liên quan khác
- Xử lý sự cố
Khi clone code một app về VPS để chạy (Docker), nên đặt nó ở thư mục nào cho chuẩn
production mà không phải sudo mỗi lần git pull? Bài này chốt một quy ước: đặt mỗi
app trong /opt/apps/<app-name>, chown thư mục cha một lần duy nhất, từ đó
clone/pull/sửa .env đều không cần sudo.
Trong toàn bài, chỗ nào ghi <...> là bạn điền giá trị thật của mình:
<username>- user thường (non-root) bạn đã tạo trên VPS, xem Cấu hình VPS mới. Ví dụ:alice.<repo-url>- URL git của app cần deploy. Ví dụ:[email protected]:<your-user>/<your-repo>.git.<app-name>- tên thư mục app, chính là tên repo dogit clonetự tạo (không cần đặt trước). Ví dụ repomy-api.gitsẽ tạo thư mụcmy-api.
Tóm tắt thao tác nhanh
# 1. Làm 1 LẦN DUY NHẤT (cả đời VPS): tạo /opt/apps thuộc về user của bạn
sudo install -d -o <username> -g <username> /opt/apps
# 2. Mỗi app mới - KHÔNG cần sudo, KHÔNG cần đặt tên trước:
cd /opt/apps
git clone <repo-url> # tự tạo /opt/apps/<app-name> theo tên repo
cd /opt/apps/<app-name>
# 3. Kiểm tra (lưu ý: /opt/apps KHÔNG nằm trong ~, phải ls /opt)
ls -ld /opt/apps # owner phải là <username>, mode 755TUYỆT ĐỐI KHÔNG chown cả /opt. Docker đã tạo sẵn /opt/containerd (thuộc root)
trong đó. Chỉ chown đúng /opt/apps.
Các bước thực hiện
Bước 1: Tạo thư mục cha /opt/apps (chỉ 1 lần cho cả VPS)
/opt mặc định thuộc root, nên không clone thẳng vào được nếu không sudo. Cách gọn
nhất là tạo sẵn một thư mục cha /opt/apps và gán quyền sở hữu cho user của bạn một
lần duy nhất:
sudo install -d -o <username> -g <username> /opt/appsinstall -d=mkdir+ set owner/group + mode 755 gọn trong một lệnh.- chown (change owner) = đổi chủ sở hữu thư mục. Ở đây gán
/opt/appscho<username>để về sau khỏi cần sudo. - mode 755 = bộ quyền: chủ sở hữu (bạn) được đọc/ghi/chạy; user khác chỉ được đọc và đi vào, không sửa.
- Thay
<username>bằng user thường của bạn (ví dụ user tạo ở bài Cấu hình VPS mới).
Bước 2: Clone app mới (từ giờ không cần sudo)
Vì /opt/apps giờ đã thuộc về bạn, git clone sẽ tự tạo thư mục con theo tên repo mà
không cần đặt tên trước, không cần sudo:
cd /opt/apps
git clone <repo-url> # vd: tạo /opt/apps/my-api
cd /opt/apps/<app-name>Tạo file .env trực tiếp trong thư mục app này, rồi docker compose up -d như bình
thường.
Bước 3: Cấu trúc thư mục thu được (toàn cảnh từ gốc /)
/opt/apps và home của bạn (/home/<username>, tức ~) là 2 nhánh tách biệt từ
gốc /. Đây là lý do ls ~ sẽ không thấy app:
/ <- gốc hệ thống (2 nhánh tách biệt bên dưới)
+-- home/
| +-- <username>/ <- ~ (home của bạn) - APP KHÔNG nằm ở đây
| +-- <your-folders>/ <- các thư mục cá nhân khác của bạn
+-- opt/
+-- apps/ <- THUỘC <username>: clone/pull KHÔNG cần sudo
| +-- <app-name>/
| | +-- .git/
| | +-- Dockerfile
| | +-- docker-compose.yml
| | +-- .env <- tạo trực tiếp ở đây
| | +-- src/ ...
| +-- <app-2>/
+-- containerd/ <- của Docker (root): ĐỪNG đụng vàoCâu hỏi thường gặp
Vì sao chọn /opt thay vì ~/apps?
Sau khi đã chown, workflow hằng ngày của /opt/apps và ~/apps y hệt nhau (đều
không cần sudo). Chọn /opt vì:
- Convention production - theo chuẩn FHS (Filesystem Hierarchy Standard - quy
ước bố trí thư mục chuẩn trên Linux),
/optlà nơi dành cho phần mềm ứng dụng cài thêm (add-on). Người bàn giao/quản trị sau quen tìm app ở/opt, không soi/home. - Không dính account cá nhân - lỡ sau này xóa/đổi user thì app không bị kẹt trong home của user đó.
Nếu bạn thích ~/apps thì vẫn hoàn toàn ổn về mặt kỹ thuật - không thua thiệt gì. Đây
là lựa chọn convention, không phải đúng/sai.
ls ~ không thấy /opt/apps?
Bình thường (xem sơ đồ cây ở Bước 3). /opt/apps bắt đầu bằng / nên nằm ở gốc hệ
thống (/opt), khác cây với home của bạn (/home/<username>). Muốn thấy thì:
ls /opt # hoặc
cd /opt/appsNhiều người cùng quản trị VPS (tùy chọn)
Nếu có nhiều admin, thay vì gán /opt/apps cho một user, tạo group deploy + bật
setgid để file mới luôn kế thừa group:
sudo groupadd -f deploy
sudo usermod -aG deploy <username>
sudo install -d -o root -g deploy -m 2775 /opt/apps # 2775 = setgid
# logout/login lại cho group có hiệu lực- setgid (số
2đứng đầu trong2775): mọi file/thư mục tạo trong/opt/appssẽ tự thừa hưởng groupdeploy, nhờ vậy admin nào trong group cũng đọc/sửa được - không cần chỉnh quyền thủ công. - Sau này thêm người:
sudo usermod -aG deploy <new-user>(rồi user đó logout/login lại).
Vị trí các thành phần liên quan khác
Bài này chỉ bàn chỗ đặt code app. Các thành phần khác đặt nơi riêng:
- Caddy reverse proxy: đặt riêng, KHÔNG nằm trong thư mục app. Tùy cách bạn chạy:
Caddy trên host (cài qua apt) thì config ở
/etc/caddy/Caddyfile; Caddy trong Docker thì config ở~/services/caddy/(xem hướng dẫn Caddy reverse proxy). Dù cách nào, Caddy là hạ tầng dựng một lần nên không để chung trong/opt/apps. - Database: dùng Postgres sẵn trên host hay bundle trong Docker - xem Cài đặt PostgreSQL trên VPS và SSH tunnel tới database từ máy local.
- Data có state (db, file upload): ưu tiên Docker named volume (Docker tự quản
lý ở
/var/lib/docker/volumes, không mất khi rebuild container) hoặc bind mount (map thẳng một thư mục như./datatrong app vào container). Tránh để data sống bên trong container vì rebuild là mất sạch.
Xử lý sự cố
| Triệu chứng | Cách xử lý |
|---|---|
git clone trong /opt/apps báo Permission denied | Chưa làm Bước 1 hoặc owner sai. Chạy ls -ld /opt/apps: owner phải là <username>. Nếu chưa đúng, chạy sudo chown <username>:<username> /opt/apps (chỉ đúng thư mục này, không phải cả /opt) |
ls ~ không thấy app | Bình thường - app nằm ở /opt/apps, không nằm trong home. Dùng ls /opt/apps |
Đã thêm user vào group deploy nhưng vẫn không ghi được | Group chỉ có hiệu lực sau khi user đó logout/login lại |