Hướng dẫn tối ưu và chạy Sonatype Nexus 3 trên VPS 1 Gi RAM
Bài viết này chia sẻ cách tự_host kho Docker riêng tư và lưu trữ artifact bằng Sonatype Nexus 3 trên một chiếc VPS chỉ có 1 GiB RAM. Tìm hiểu về các cấu hình JVM, tối ưu hóa bộ nhớ, swap và Traefik để vận hành ổn định trong môi trường sản xuất.

Mỗi lần tôi đẩy một Docker image lên Docker Hub sử dụng gói miễn phí, tôi lại thường xuyên lo ngại về giới hạn tốc độ (rate limits), chính sách lưu giữ và sự phụ thuộc dần dần vào nhà cung cấp. Đối với các dự án cá nhân hoặc các nhóm nhỏ, việc tự_host một registry riêng tư mang lại nhiều lợi ích:
- Không giới hạn tốc độ khi kéo (pull) image — điều cực kỳ quan trọng đối với các pipeline CI/CD.
- Lưu trữ image riêng tư mà không cần trả phí cho các dịch vụ cloud registry.
- Kho lưu trữ tập trung cho Docker images, npm packages, Maven artifacts và nhiều hơn nữa — tất cả dưới một mái nhà.
- Kiểm soát hoàn toàn việc lưu giữ và truy cập.
Tuy nhiên, nhược điểm là Sonatype Nexus 3 là một ứng dụng Java. Nó được xây dựng cho các máy chủ doanh nghiệp, không phải cho các instance VPS giá rẻ. Tài liệu chính thức khuyến nghị tối thiểu 8 GiB RAM. Việc chạy nó trên 1 GiB thực sự là một bài toán kỹ thuật đầy thách thức về tài nguyên — và đó chính là loại bài toán tôi yêu thích.
Cài đặt và Cấu trúc
Hạ tầng
| Thành phần | Chi tiết |
|---|---|
| VPS | 1 vCPU, 1 GiB RAM, 25 GiB SSD |
| Hệ điều hành | Ubuntu 22.04 LTS |
| Phiên bản Nexus | 3.90.2-alpine |
| Cơ sở dữ liệu | PostgreSQL (bên ngoài, trên cùng host) |
| Reverse proxy | Traefik v3 với TLS tự động |
| DNS | repository.bitnoises.com (UI), registry.bitnoises.com (Docker) |
Kiến trúc
Kiến trúc hệ thống Nexus
Traefik sẽ xử lý tất cả việc kết thúc TLS. Nexus không bao giờ "thấy" lưu lượng HTTPS thô — nó chỉ giao tiếp qua HTTP đơn giản bên trong, giúp đơn giản hóa cấu hình considerably.
Vấn đề về Bộ nhớ
Dưới đây là bức tranh thực tế về ngân sách RAM của tôi trước khi Nexus khởi động:
Tổng RAM: 957 Mi
Hệ điều hành + kernel: ~150 Mi
Docker daemon: ~50 Mi
Traefik: ~30 Mi
PostgreSQL: ~80 Mi
─────────────────────────────
Dành cho Nexus: ~647 Mi
Nexus là một ứng dụng JVM. Dấu chân bộ nhớ của nó có hai thành phần chính:
- Heap (
-Xmx) — cấp phát đối tượng, được quản lý bởi bộ thu gom rác (Garbage Collector). - Direct memory (
-XX:MaxDirectMemorySize) — bộ đệm ngoài heap, được sử dụng nhiều cho I/O.
Khuyến nghị mặc định của Sonatype là -Xmx2703m. Trên VPS của tôi, điều đó sẽ yêu cầu gấp 4 lần lượng RAM khả dụng. JVM sẽ ngay lập tức bắt đầu swap và container sẽ bị OOM-killed (kết thúc do thiếu bộ nhớ) trong vài phút.
Giải pháp ở đây là giảm quy mô một cách quyết liệt nhưng cẩn thận.
Tinh chỉnh JVM
-Xms128m # Bắt đầu với heap nhỏ, tăng khi cần
-Xmx384m # Giới hạn cứng của heap
-XX:MaxDirectMemorySize=192m # Giới hạn bộ đệm off-heap
-XX:+UseG1GC # GC tốt hơn dưới áp lực bộ nhớ
-XX:MaxGCPauseMillis=300 # Mục tiêu thời gian dừng GC
-XX:G1HeapRegionSize=4m # Vùng nhỏ hơn = lãng phí ít hơn
-XX:+UseStringDeduplication # G1 loại bỏ chuỗi trùng lặp (~tiết kiệm 5-10% heap)
-XX:SoftRefLRUPolicyMSPerMB=0 # Xóa tham chiếu mềm tích cực khi áp lực
Thông tin quan trọng là tách biệt -Xms và -Xmx. Bắt đầu ở mức 128m có nghĩa là JVM tiêu thụ tối thiểu RAM khi khởi động và chỉ mở rộng heap khi Nexus thực sự cần. Trên một registry cá nhân khá yên tĩnh, hiếm khi nó cần mở rộng quá 250–300m trong thực tế.
Tổng dấu chân JVM: ~680m. Giới hạn mem_limit của Docker được đặt ở mức 700m, để lại 20m bộ đệm cho sự biến đổi của JVM.
Tầm quan trọng của Swap
Tuyệt đối không chạy ứng dụng JVM trên máy không có swap.
Swap trên SSD không phải là một chiến lược hiệu suất — nó là một tấm lưới an toàn. Nếu không có nó, Linux OOM killer sẽ chấm dứt container của bạn ngay lập tức khi nó vượt quá giới hạn bộ nhớ, không có cảnh báo và không có tắt xuống an toàn. Nexus không xử lý tốt việc bị giết đột ngột; bạn có nguy cơ làm hỏng cơ sở dữ liệu hoặc blob store ở trạng thái không nhất quán.
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
# Chỉ sử dụng swap như một giải pháp cuối cùng
echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p
Với vm.swappiness=10, Linux ưu tiên mạnh mẽ việc giữ dữ liệu trong RAM và chỉ swap ra file khi thực sự áp lực. SSD chịu một chút tác động nhỏ, nhưng dịch vụ của bạn sẽ sống sót qua các đỉnh (spikes).
Sau khi thêm swap, bức tranh bộ nhớ của tôi trông như sau:
$ free -h
total used free buff/cache available
Mem: 957Mi 194Mi 91Mi 671Mi 594Mi
Swap: 2.0Gi 125Mi 1.9Gi
Cột buff/cache (671Mi) trông có vẻ đáng báo động nhưng thực tế không phải vậy — Linux sử dụng RAM trống làm đĩa cache, và cache đó có thể bị loại bỏ ngay lập tức khi Nexus cần. Cột available (594Mi) mới là con số quan trọng.
File cấu hình compose.yml
services:
nexus:
image: sonatype/nexus3:3.90.2-alpine
container_name: nexus
restart: unless-stopped
user: "200:200"
environment:
INSTALL4J_ADD_VM_PARAMS: >-
-Xms128m
-Xmx384m
-XX:MaxDirectMemorySize=192m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=300
-XX:G1HeapRegionSize=4m
-XX:+UseStringDeduplication
-XX:SoftRefLRUPolicyMSPerMB=0
-Djava.util.prefs.userRoot=/nexus-data/javaprefs
-Dnexus.datastore.enabled=true
-Dnexus-ssl-proxy=true
NEXUS_DATASTORE_NEXUS_JDBCURL: jdbc:postgresql://${DB_HOST}:5432/${DB_NAME}
NEXUS_DATASTORE_NEXUS_USERNAME: ${DB_USER}
NEXUS_DATASTORE_NEXUS_PASSWORD: ${DB_PASSWORD}
volumes:
- "./data:/nexus-data"
mem_limit: 700m
memswap_limit: 1400m # 700m RAM + 700m swap headroom
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8081/service/rest/v1/status || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 180s # Nexus khởi động chậm trên cấu hình thấp
networks:
- traefiknetwork
- infra
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefiknetwork"
# UI
- "traefik.http.routers.nexus-ui.rule=Host(`${NEXUS_HOST}`)"
- "traefik.http.routers.nexus-ui.entrypoints=websecure"
- "traefik.http.routers.nexus-ui.tls=true"
- "traefik.http.routers.nexus-ui.tls.certresolver=letsencrypt"
- "traefik.http.routers.nexus-ui.service=nexus-ui"
- "traefik.http.services.nexus-ui.loadbalancer.server.port=8081"
# Docker registry
- "traefik.http.routers.nexus-docker.rule=Host(`${REGISTRY_HOST}`)"
- "traefik.http.routers.nexus-docker.entrypoints=websecure"
- "traefik.http.routers.nexus-docker.tls=true"
- "traefik.http.routers.nexus-docker.tls.certresolver=letsencrypt"
- "traefik.http.routers.nexus-docker.service=nexus-docker"
- "traefik.http.services.nexus-docker.loadbalancer.server.port=5000"
- "traefik.http.middlewares.docker-headers.headers.customrequestheaders.Docker-Distribution-Api-Version=registry/2.0"
- "traefik.http.middlewares.nexus-docker-buffering.buffering.maxRequestBodyBytes=0"
- "traefik.http.routers.nexus-docker.middlewares=docker-headers,nexus-docker-buffering"
networks:
traefiknetwork:
external: true
infra:
external: true
Một số điểm đáng lưu ý
user: "200:200" — Ảnh Nexus nội bộ chạy dưới UID 200. Việc đặt rõ ràng điều này ngăn chặn việc thực thi ngẫu nhiên dưới quyền root. Thư mục ./data phải được sở hữu trước: sudo chown -R 200:200 ./data.
start_period: 180s — Nếu không có cái này, Docker sẽ đánh dấu container là không khỏe (unhealthy) trước khi nó hoàn tất khởi động, điều này có thể kích hoạt vòng lặp khởi động lại. Trên phần cứng hạn chế, Nexus mất 2–3 phút để khởi động.
maxRequestBodyBytes=0 — Label Traefik quan trọng nhất cho Docker registry. Nếu không có nó, việc đẩy bất kỳ lớp image nào lớn hơn giới hạn kích thước body mặc định của Traefik (2m) sẽ thất bại với lỗi 413 khó hiểu.
PostgreSQL vs Database tích hợp
Nexus 3 hỗ trợ hai backend cơ sở dữ liệu: H2 tích hợp và PostgreSQL bên ngoài. Tôi chọn PostgreSQL vì vài lý do:
- Độ tin cậy — H2 ổn định cho phát triển, nhưng tôi đã thấy nó bị hỏng khi JVM bị kill đột ngột. PostgreSQL xử lý việc tắt xuống bẩn một cách nhẹ nhàng hơn.
- Khả năng quan sát — Tôi có thể truy vấn trực tiếp cơ sở dữ liệu để kiểm tra trạng thái, chạy sao lưu và giám sát số lượng kết nối.
- Tính nhất quán — PostgreSQL đang chạy trên hạ tầng của tôi cho các dịch vụ khác. Một phần di chuyển ít hơn.
Sự đánh đổi: không có file admin.password trên cài đặt dựa trên PostgreSQL. Thông tin đăng nhập mặc định đơn giản là admin / admin123, và Nexus buộc thay đổi mật khẩu khi đăng nhập lần đầu.
Cấu hình Docker Registry
Sau khi đăng nhập lần đầu:
Bật Bearer Token Realm
Administration → Security → Realms → chuyển Docker Bearer Token Realm sang Active.
Đây là nguyên nhân phổ biến nhất của lỗi 401 Unauthorized khi sử dụng docker login. Nó phải được bật.
Tạo Repository
Administration → Repository → Repositories → Create repository → docker (hosted)
Đặt cổng HTTP thành 5000 (đây là cổng Traefik sẽ route tới), bỏ chọn HTTPS (Traefik sẽ xử lý việc đó), và đặt chính sách triển khai theo sở thích của bạn.
Tích hợp CI/CD
Tôi đã tạo một người dùng ci chuyên dụng với vai trò tối thiểu, tuân theo nguyên tắc đặc quyền tối thiểu. Vai trò này chỉ có các đặc quyền cần thiết để đẩy và kéo từ repository Docker — không có quyền admin, không có quyền truy cập các repository khác.
Trong GitLab CI/CD:
stages:
- push
push-alpine-to-nexus:
stage: push
image: docker:29.2.1
services:
- docker:29.2.1-dind
variables:
DOCKER_TLS_CERTDIR: ""
script:
# Đăng nhập vào private registry
- echo "$NEXUS_PASSWORD" | docker login registry.bitnoises.com \
--username "$NEXUS_USER" \
--password-stdin
# Kéo image Alpine từ Docker Hub
- docker pull alpine:latest
# Gán thẻ (tag) image cho private registry
- docker tag alpine:latest registry.bitnoises.com/alpine:latest
# Đẩy image lên Nexus
- docker push registry.bitnoises.com/alpine:latest
Sử dụng commit SHA làm thẻ image thay vì latest mang lại cho bạn khả năng truy xuất đầy đủ — mọi lần triển khai đều có thể truy ngược lại một commit chính xác.
Quản lý ổ cứng
SSD là ràng buộc khác. 25 GiB nghe có vẻ nhiều cho đến khi bạn bắt đầu lưu trữ Docker images — một image ứng dụng Node.js điển hình là 200–400 MiB, và bạn tích lũy nhiều phiên bản rất nhanh.
Chính sách dọn dẹp (Cleanup Policies)
Trong Administration → Repository → Cleanup Policies, tôi đã tạo một chính sách loại bỏ:
- Các thành phần cũ hơn 30 ngày
- Các thành phần không được tải xuống trong 14 ngày
Đính kèm vào repository docker-hosted và lên lịch là một tác vụ hàng tuần, điều này giữ cho blob store không phát triển vô hạn.
Bài học kinh nghiệm
Bộ nhớ JVM không chỉ là Heap. Nhiều hướng dẫn nói "đặt -Xmx bằng một nửa RAM của bạn" mà không đề cập đến bộ nhớ trực tiếp, metaspace, code cache, hoặc stack luồng JVM. Dấu chân thực tế là heap + direct + ~100m nội bộ của JVM. Hãy ngân sách cho tất cả.
Swap trước mọi thứ khác. Tôi gần như triển khai mà không có swap vì "swap trên SSD chậm". Swap trên SSD với swappiness 10% thực sự hiếm khi được sử dụng trong hoạt động bình thường, nhưng nó đã cứu tôi khỏi OOM kills nhiều lần trong quá trình khởi động Nexus khi áp lực bộ nhớ cao nhất.
Thứ tự label Traefik rất quan trọng. Label middlewares phải tham chiếu đến tên middleware được định nghĩa trong các label khác trong cùng dịch vụ. Nếu bạn định nghĩa docker-headers và nexus-docker-buffering nhưng chỉ tham chiếu một cái trong label router, cái kia sẽ âm thầm không hoạt động.
start_period không phải tùy chọn cho dịch vụ chậm. start_period của healthcheck Docker là thời gian chờ ân hạn trước khi các lần kiểm tra thất bại được tính vào retries. Đối với dịch vụ mất 2–3 phút để khởi động, việc đặt cái này thành 30s có nghĩa là Docker sẽ khởi động lại container trước khi nó thậm chí đã bắt đầu xong — tạo ra một vòng lặp khởi động lại vô hạn trông giống như vấn đề bộ nhớ.
Thông tin đăng nhập mặc định của PostgreSQL không nằm trong logs. Đến từ Nexus sử dụng H2 nơi một file admin.password được tạo ra, điều này đã làm tôi bất ngờ. Cài đặt dựa trên PostgreSQL chỉ đơn giản sử dụng admin / admin123 mà không có file hoặc mục log nào chỉ ra điều này.
Kết quả
Một Docker registry và artifact store riêng tư đầy đủ chức năng, chạy đáng tin cậy trên phần cứng chỉ tốn vài euro một tháng. Việc sử dụng bộ nhớ ở trạng thái ổn định:
$ docker stats nexus --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM %
nexus 0.3% 412MiB / 700MiB 58.9%
58% sử dụng bộ nhớ ở trạng thái rảnh, với 42% dung lượng trống trước khi chạm giới hạn cứng và 700m swap khả dụng ngoài ra. Đối với một dự án hạ tầng cá nhân, đó là một biên độ an toàn thoải mái.
Toàn bộ cấu hình, script và tài liệu có sẵn trên GitHub: gitlab.com/hanatole/nexus
Bài viết liên quan

Phần mềm
Anthropic ra mắt Claude Opus 4.7: Nâng cấp mạnh mẽ cho lập trình nhưng vẫn thua Mythos Preview
16 tháng 4, 2026

Công nghệ
Qwen3.6-35B-A3B: Quyền năng Lập trình Agentic, Nay Đã Mở Cửa Cho Tất Cả
16 tháng 4, 2026

Công nghệ
Spotify thắng kiện 322 triệu USD từ nhóm pirate Anna's Archive nhưng đối mặt với bài toán thu hồi
16 tháng 4, 2026
