Vũ Văn HảiFull-stack · AI-native
Hướng dẫnBlog
Trao đổi dự án

© 2026 Vũ Văn Hải · Viết từ kinh nghiệm triển khai thật.

Trang chủHướng dẫnBlogRSS
  1. Hướng dẫn
  2. /VPS
  3. /Đặt app ở đâu trên VPS? Cấu trúc thư mục chuẩn /opt/apps

Đặ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.

Cập nhật: 21 thg 9, 20266 phút đọc
VPSDockerGitUbuntu
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 do git clone tự tạo (không cần đặt trước). Ví dụ repo my-api.git sẽ tạo thư mục my-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 755

TUYỆ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/apps
  • install -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/apps cho <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ào

Câ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), /opt là 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/apps

Nhiề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 trong 2775): mọi file/thư mục tạo trong /opt/apps sẽ tự thừa hưởng group deploy, 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ư ./data trong 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ứngCách xử lý
git clone trong /opt/apps báo Permission deniedChư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 appBì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 đượcGroup chỉ có hiệu lực sau khi user đó logout/login lại
Bài trướcPhục vụ media qua Cloudflare CDN + Backblaze B2 (egress miễn phí)

Bài liên quan

  • Quay ngược commit Git và cập nhật an toàn trên VPS

    Cách đưa dự án về một commit cũ, force push lên remote, rồi reset code trên VPS cho khớp mà không dính merge conflict, sau đó rebuild service và xử lý cache.

    Công cụ dev

    Công cụ dev
  • Trang bảo trì tự động với Caddy khi deploy Docker

    Dùng handle_errors của Caddy để bắt lỗi 502 Bad Gateway lúc container đang rebuild và tự động hiển thị một trang HTML bảo trì, không cần bật tắt thủ công.

    VPS

    VPS
  • Cài Caddy làm reverse proxy trên VPS bằng Docker

    Dựng Caddy làm reverse proxy bằng Docker trên VPS mới: tạo network dùng chung, viết Caddyfile và docker-compose, tự động có HTTPS, rồi thêm domain mới chỉ bằng một lệnh reload không downtime.

    VPS

    VPS

Viết bởi Vũ Văn Hải

Tôi là Hải, full-stack developer ở TP. Hồ Chí Minh. Các bài ở đây đúc kết từ những hệ thống tôi tự dựng và vận hành. Cần dựng hoặc gỡ rối một hệ thống tương tự? Cứ nhắn tôi.

Trao đổi dự ánXem thêm hướng dẫn

Thấy sai sót hoặc lệnh không còn chạy? Báo cho tôi

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ố