| Component | Details |
| ------------- | ------------------------------------------------------------------|
| VPS | 1 vCPU, 1 Gi RAM, 25 Gi SSD |
| OS | Ubuntu 22.04 LTS |
| Nexus version | 3.90.2-alpine |
| Database | PostgreSQL (external, on the same host) |
| Reverse proxy | Traefik v3 with automatic TLS |
| DNS | `repository.bitnoises.com` (UI), `registry.bitnoises.com` (Docker)|
| Component | Details |
| ------------- | ------------------------------------------------------------------|
| VPS | 1 vCPU, 1 Gi RAM, 25 Gi SSD |
| OS | Ubuntu 22.04 LTS |
| Nexus version | 3.90.2-alpine |
| Database | PostgreSQL (external, on the same host) |
| Reverse proxy | Traefik v3 with automatic TLS |
| DNS | `repository.bitnoises.com` (UI), `registry.bitnoises.com` (Docker)|
| Component | Details |
| ------------- | ------------------------------------------------------------------|
| VPS | 1 vCPU, 1 Gi RAM, 25 Gi SSD |
| OS | Ubuntu 22.04 LTS |
| Nexus version | 3.90.2-alpine |
| Database | PostgreSQL (external, on the same host) |
| Reverse proxy | Traefik v3 with automatic TLS |
| DNS | `repository.bitnoises.com` (UI), `registry.bitnoises.com` (Docker)|
Total RAM: 957 Mi
OS + kernel: ~150 Mi
Docker daemon: ~50 Mi
Traefik: ~30 Mi
PostgreSQL: ~80 Mi
─────────────────────────────
Available for Nexus: ~647 Mi
Total RAM: 957 Mi
OS + kernel: ~150 Mi
Docker daemon: ~50 Mi
Traefik: ~30 Mi
PostgreSQL: ~80 Mi
─────────────────────────────
Available for Nexus: ~647 Mi
Total RAM: 957 Mi
OS + kernel: ~150 Mi
Docker daemon: ~50 Mi
Traefik: ~30 Mi
PostgreSQL: ~80 Mi
─────────────────────────────
Available for Nexus: ~647 Mi
-Xms128m # Start heap small, grow as needed
-Xmx384m # Hard heap ceiling
-XX:MaxDirectMemorySize=192m # Off-heap buffer ceiling
-XX:+UseG1GC # Better GC under memory pressure
-XX:MaxGCPauseMillis=300 # GC pause target
-XX:G1HeapRegionSize=4m # Smaller regions = less wasted space
-XX:+UseStringDeduplication # G1 deduplicates identical strings (~5-10% heap savings)
-XX:SoftRefLRUPolicyMSPerMB=0 # Aggressively clear soft references under pressure
-Xms128m # Start heap small, grow as needed
-Xmx384m # Hard heap ceiling
-XX:MaxDirectMemorySize=192m # Off-heap buffer ceiling
-XX:+UseG1GC # Better GC under memory pressure
-XX:MaxGCPauseMillis=300 # GC pause target
-XX:G1HeapRegionSize=4m # Smaller regions = less wasted space
-XX:+UseStringDeduplication # G1 deduplicates identical strings (~5-10% heap savings)
-XX:SoftRefLRUPolicyMSPerMB=0 # Aggressively clear soft references under pressure
-Xms128m # Start heap small, grow as needed
-Xmx384m # Hard heap ceiling
-XX:MaxDirectMemorySize=192m # Off-heap buffer ceiling
-XX:+UseG1GC # Better GC under memory pressure
-XX:MaxGCPauseMillis=300 # GC pause target
-XX:G1HeapRegionSize=4m # Smaller regions = less wasted space
-XX:+UseStringDeduplication # G1 deduplicates identical strings (~5-10% heap savings)
-XX:SoftRefLRUPolicyMSPerMB=0 # Aggressively clear soft references under pressure
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab # Only use swap as a last resort
echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab # Only use swap as a last resort
echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab # Only use swap as a last resort
echo 'vm.swappiness=10' >> /etc/sysctl.conf
sysctl -p
$ free -h total used free buff/cache available
Mem: 957Mi 194Mi 91Mi 671Mi 594Mi
Swap: 2.0Gi 125Mi 1.9Gi
$ free -h total used free buff/cache available
Mem: 957Mi 194Mi 91Mi 671Mi 594Mi
Swap: 2.0Gi 125Mi 1.9Gi
$ free -h total used free buff/cache available
Mem: 957Mi 194Mi 91Mi 671Mi 594Mi
Swap: 2.0Gi 125Mi 1.9Gi
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 is slow to boot on constrained hardware 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
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 is slow to boot on constrained hardware 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
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 is slow to boot on constrained hardware 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
stages: - push push-alpine-to-nexus: stage: push image: docker:29.2.1 services: - docker:29.2.1-dind variables: DOCKER_TLS_CERTDIR: "" script: # Log in to the private registry - echo "$NEXUS_PASSWORD" | docker login registry.bitnoises.com \ --username "$NEXUS_USER" \ --password-stdin # Pull Alpine image from Docker Hub - docker pull alpine:latest # Tag the image for the private registry - docker tag alpine:latest registry.bitnoises.com/alpine:latest # Push the image to Nexus - docker push registry.bitnoises.com/alpine:latest
stages: - push push-alpine-to-nexus: stage: push image: docker:29.2.1 services: - docker:29.2.1-dind variables: DOCKER_TLS_CERTDIR: "" script: # Log in to the private registry - echo "$NEXUS_PASSWORD" | docker login registry.bitnoises.com \ --username "$NEXUS_USER" \ --password-stdin # Pull Alpine image from Docker Hub - docker pull alpine:latest # Tag the image for the private registry - docker tag alpine:latest registry.bitnoises.com/alpine:latest # Push the image to Nexus - docker push registry.bitnoises.com/alpine:latest
stages: - push push-alpine-to-nexus: stage: push image: docker:29.2.1 services: - docker:29.2.1-dind variables: DOCKER_TLS_CERTDIR: "" script: # Log in to the private registry - echo "$NEXUS_PASSWORD" | docker login registry.bitnoises.com \ --username "$NEXUS_USER" \ --password-stdin # Pull Alpine image from Docker Hub - docker pull alpine:latest # Tag the image for the private registry - docker tag alpine:latest registry.bitnoises.com/alpine:latest # Push the image to Nexus - docker push registry.bitnoises.com/alpine:latest
# How much is Nexus actually using?
du -sh ./data/blobs/ # Docker layer cache on the host
docker system df # Full picture
df -h
# How much is Nexus actually using?
du -sh ./data/blobs/ # Docker layer cache on the host
docker system df # Full picture
df -h
# How much is Nexus actually using?
du -sh ./data/blobs/ # Docker layer cache on the host
docker system df # Full picture
df -h
$ docker stats nexus --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM %
nexus 0.3% 412MiB / 700MiB 58.9%
$ docker stats nexus --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM %
nexus 0.3% 412MiB / 700MiB 58.9%
$ docker stats nexus --no-stream
CONTAINER CPU % MEM USAGE / LIMIT MEM %
nexus 0.3% 412MiB / 700MiB 58.9% - No rate limits on pulls (critical in CI/CD pipelines)
- Private images without paying for a cloud registry
- A single artifact store for Docker images, npm packages, Maven artifacts, and more — all under one roof
- Full control over retention and access - Heap (-Xmx) — object allocations, managed by the garbage collector
- Direct memory (-XX:MaxDirectMemorySize) — off-heap buffers, used heavily for I/O - Reliability — H2 is fine for development, but I've seen it corrupt under abrupt JVM kills. PostgreSQL handles dirty shutdowns gracefully.
- Observability — I can query the database directly to inspect state, run backups, and monitor connection counts.
- Consistency — PostgreSQL is already running on my infrastructure for other services. One less moving part. - Components older than 30 days
- Components not downloaded in 14 days