DevOpsil
Nexus Repository
88%
Needs Review

Nexus Repository Docker Registry Proxy Configuration For Air-Gapped Environments

Raafay AsifRaafay Asif11 min read

Complete Guide to Nexus Repository Docker Registry Proxy Configuration for Air-Gapped Environments

Air-gapped environments are a reality for many organizations dealing with sensitive data, regulatory compliance, or high-security requirements. When you can't simply docker pull nginx:latest from the internet, you need a robust solution that bridges the gap between security requirements and developer productivity. That's where Nexus Repository Manager comes in as a Docker registry proxy.

I've spent countless hours setting up and troubleshooting Nexus installations in various air-gapped environments, from financial institutions to government contractors. This guide will walk you through everything you need to know to get Docker registry proxying working reliably in your isolated network.

Understanding Air-Gapped Docker Registry Challenges

Before diving into the configuration, let's understand what we're solving. In an air-gapped environment, your primary challenges are:

  1. No direct internet access for pulling Docker images
  2. Image synchronization between external and internal networks
  3. Certificate management in isolated environments
  4. Storage and bandwidth optimization for large container images
  5. Maintaining image integrity and security scanning

Nexus Repository Manager addresses these challenges by acting as a caching proxy that can pre-populate images and serve them internally even when disconnected from the internet.

Prerequisites and Environment Setup

For this setup, you'll need:

  • Nexus Repository Manager OSS 3.x or Pro (I recommend Pro for production air-gapped environments)
  • A Linux server with at least 8GB RAM and 100GB storage (more for production)
  • Docker installed on both Nexus server and client machines
  • SSL certificates (self-signed acceptable for internal use)
  • Network access between Nexus server and Docker clients

Let's start with the basic server preparation:

# Update system and install required packages
sudo yum update -y
sudo yum install -y java-1.8.0-openjdk wget unzip

# Create nexus user
sudo useradd -r -m -c "Nexus Repository Manager" -d /opt/sonatype-nexus -s /bin/bash nexus

# Download and install Nexus (adjust version as needed)
cd /tmp
wget https://download.sonatype.com/nexus/3/nexus-3.41.1-01-unix.tar.gz
tar -xzf nexus-3.41.1-01-unix.tar.gz
sudo mv nexus-3.41.1-01 /opt/sonatype-nexus/nexus
sudo mv sonatype-work /opt/sonatype-nexus/

# Set ownership
sudo chown -R nexus:nexus /opt/sonatype-nexus/

SSL Certificate Configuration

In air-gapped environments, proper SSL configuration is crucial. Here's how to set up certificates that Docker will trust:

# Generate self-signed certificate for internal use
sudo mkdir -p /opt/sonatype-nexus/ssl
cd /opt/sonatype-nexus/ssl

# Create certificate configuration
cat > nexus.conf << EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[dn]
CN=nexus.internal.local
O=Internal Registry
C=US

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = nexus.internal.local
DNS.2 = nexus
IP.1 = 10.0.1.100
IP.2 = 127.0.0.1
EOF

# Generate private key and certificate
sudo openssl req -new -x509 -key nexus.key -out nexus.crt -config nexus.conf -extensions v3_req -days 3650
sudo openssl genrsa -out nexus.key 2048
sudo openssl req -new -x509 -key nexus.key -out nexus.crt -config nexus.conf -extensions v3_req -days 3650

# Convert to PKCS12 format for Nexus
sudo openssl pkcs12 -export -out nexus.p12 -inkey nexus.key -in nexus.crt -password pass:changeit

sudo chown nexus:nexus nexus.*

Nexus Repository Configuration

Now let's configure Nexus with proper JVM settings and SSL support:

# Configure Nexus JVM settings
sudo -u nexus cat > /opt/sonatype-nexus/nexus/bin/nexus.vmoptions << EOF
-Xms2g
-Xmx2g
-XX:MaxDirectMemorySize=3g
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:+UseG1GC
-XX:+UseStringDeduplication
-Dkaraf.home=.
-Dkaraf.base=.
-Dkaraf.etc=etc/karaf
-Djava.util.logging.config.file=etc/karaf/java.util.logging.properties
-Dkaraf.data=../sonatype-work/nexus3
-Dkaraf.log=../sonatype-work/nexus3/log
-Djava.io.tmpdir=../sonatype-work/nexus3/tmp
-Dkaraf.startLocalConsole=false
-Djavax.net.ssl.keyStore=/opt/sonatype-nexus/ssl/nexus.p12
-Djavax.net.ssl.keyStorePassword=changeit
-Djavax.net.ssl.trustStore=/opt/sonatype-nexus/ssl/nexus.p12
-Djavax.net.ssl.trustStorePassword=changeit
EOF

# Configure Nexus properties
sudo -u nexus cat > /opt/sonatype-nexus/sonatype-work/nexus3/etc/nexus.properties << EOF
# Nexus Repository Manager configuration
nexus.scripts.allowCreation=true
nexus.security.randompassword=false
nexus.docker.hosted.port=5000
nexus.docker.proxy.port=5001
nexus.docker.group.port=5002
application-port-ssl=8443
nexus-args=\${karaf.data}/log/karaf.log,\${application-port},\${application-host}
EOF

# Create systemd service
sudo cat > /etc/systemd/system/nexus.service << EOF
[Unit]
Description=Nexus Repository Manager
After=network.target

[Service]
Type=forking
LimitNOFILE=65536
ExecStart=/opt/sonatype-nexus/nexus/bin/nexus start
ExecStop=/opt/sonatype-nexus/nexus/bin/nexus stop
User=nexus
Restart=on-abort
TimeoutSec=600

[Install]
WantedBy=multi-user.target
EOF

# Enable and start Nexus
sudo systemctl daemon-reload
sudo systemctl enable nexus
sudo systemctl start nexus

Docker Registry Proxy Repository Configuration

Once Nexus is running, access the web interface at https://your-nexus-server:8443 and follow these steps:

1. Create Docker Proxy Repository

Navigate to Administration → Repositories → Create repository → docker (proxy)

Here's the configuration I recommend for air-gapped environments:

# Basic Configuration
Name: docker-hub-proxy
Remote storage: https://registry-1.docker.io
Docker Index: Use Docker Hub
Registry API Support: V1 API support disabled (recommended)

# Proxy Configuration
Auto-blocking enabled: false
Blocked: false
Content Max Age: 1440 (24 hours)
Metadata Max Age: 1440 (24 hours)

# Storage Configuration
Blob store: default
Strict Content Type Validation: true

# Docker Configuration
Enable Docker V1 API: false
Force basic authentication: true
V1 Enabled: false

# HTTP Configuration
User-Agent Suffix: NexusRepositoryManager/3.41.1-01

2. Create Docker Hosted Repository

For storing your own images:

Name: docker-hosted
HTTP port: 5000
Allow anonymous docker pull: false
Enable Docker V1 API: false
Blob store: default

3. Create Docker Group Repository

This combines your proxy and hosted repositories:

Name: docker-group
HTTP port: 5002
Member repositories:
  - docker-hosted
  - docker-hub-proxy
Allow anonymous docker pull: false
Enable Docker V1 API: false

Advanced Proxy Configuration

For production air-gapped environments, you'll want more sophisticated configuration:

Custom Blob Store Configuration

# Create dedicated storage for Docker images
sudo mkdir -p /data/nexus-docker-blobs
sudo chown nexus:nexus /data/nexus-docker-blobs

# In Nexus UI: Administration → Repository → Blob Stores
# Create new File blob store
# Name: docker-images
# Path: /data/nexus-docker-blobs

Cleanup Policies

Air-gapped environments need aggressive cleanup to manage storage:

# Administration → Repository → Cleanup Policies
Policy Name: docker-cleanup-policy
Format: docker
Criteria:
  - Component age: 30 days
  - Component usage: Last downloaded less than 30 days ago
  - Asset name pattern: .*-SNAPSHOT.*

Repository-Specific Configuration

For different upstream registries, create separate proxies:

# Example: Red Hat Registry Proxy
Name: redhat-registry-proxy
Remote storage: https://registry.redhat.io
Authentication Type: Username
Username: your-redhat-username
Password: your-redhat-token

# Example: Google Container Registry Proxy
Name: gcr-proxy
Remote storage: https://gcr.io
Authentication Type: Username
Username: _json_key
Password: <contents of service account JSON>

Client Docker Configuration

Configure Docker clients to use your Nexus proxy:

1. Trust the Certificate

# Copy certificate to Docker's certificate directory
sudo mkdir -p /etc/docker/certs.d/nexus.internal.local:5002
sudo cp /opt/sonatype-nexus/ssl/nexus.crt /etc/docker/certs.d/nexus.internal.local:5002/ca.crt

# Also add to system trust store
sudo cp /opt/sonatype-nexus/ssl/nexus.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract

2. Configure Docker Daemon

{
  "insecure-registries": [],
  "registry-mirrors": ["https://nexus.internal.local:5002"],
  "dns": ["10.0.1.1", "8.8.8.8"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
# Apply configuration
sudo systemctl restart docker

3. Test Docker Pull

# Login to Nexus registry
docker login nexus.internal.local:5002
Username: admin
Password: your-nexus-password

# Test pulling an image
docker pull nexus.internal.local:5002/nginx:latest

# Verify the pull worked
docker images | grep nginx

Image Pre-Population Strategies

In truly air-gapped environments, you need to pre-populate your registry. Here are several approaches:

1. Automated Image Sync Script

#!/bin/bash
# sync-docker-images.sh

NEXUS_HOST="nexus.internal.local:5002"
NEXUS_USER="admin"
NEXUS_PASS="your-password"

# List of critical images to sync
IMAGES=(
    "nginx:latest"
    "nginx:alpine"
    "redis:latest"
    "postgres:13"
    "postgres:14"
    "ubuntu:20.04"
    "ubuntu:22.04"
    "alpine:latest"
    "busybox:latest"
    "hello-world:latest"
)

# Login to registries
docker login $NEXUS_HOST -u $NEXUS_USER -p $NEXUS_PASS
docker login # Docker Hub login

sync_image() {
    local image=$1
    echo "Syncing $image..."
    
    # Pull from upstream
    if docker pull $image; then
        # Tag for internal registry
        docker tag $image $NEXUS_HOST/$image
        
        # Push to internal registry
        if docker push $NEXUS_HOST/$image; then
            echo "Successfully synced $image"
            # Clean up local copies to save space
            docker rmi $image $NEXUS_HOST/$image
        else
            echo "Failed to push $image to internal registry"
        fi
    else
        echo "Failed to pull $image from upstream"
    fi
}

# Sync all images
for image in "${IMAGES[@]}"; do
    sync_image $image
    sleep 2  # Rate limiting
done

echo "Image sync complete"

2. Bulk Image Export/Import

For completely disconnected environments:

#!/bin/bash
# export-images.sh (run on connected system)

EXPORT_DIR="/tmp/docker-images"
mkdir -p $EXPORT_DIR

IMAGES=(
    "nginx:latest"
    "redis:latest"
    "postgres:13"
    # Add your required images
)

for image in "${IMAGES[@]}"; do
    echo "Exporting $image..."
    docker pull $image
    docker save $image > "$EXPORT_DIR/${image//\//_}-${image//:/_}.tar"
done

# Create transfer package
tar -czf docker-images-$(date +%Y%m%d).tar.gz -C $EXPORT_DIR .
#!/bin/bash
# import-images.sh (run on air-gapped system)

IMPORT_DIR="/tmp/docker-images"
NEXUS_HOST="nexus.internal.local:5002"

# Extract images
tar -xzf docker-images-*.tar.gz -C $IMPORT_DIR

# Import and push to Nexus
for tarfile in $IMPORT_DIR/*.tar; do
    echo "Loading $(basename $tarfile)..."
    docker load < "$tarfile"
done

# Re-tag and push to internal registry
docker images --format "table {{.Repository}}:{{.Tag}}" | grep -v REPOSITORY | while read image; do
    if [[ ! $image == *"nexus.internal.local"* ]]; then
        echo "Pushing $image to internal registry..."
        docker tag $image $NEXUS_HOST/$image
        docker push $NEXUS_HOST/$image
    fi
done

Monitoring and Maintenance

Health Check Scripts

#!/bin/bash
# nexus-health-check.sh

NEXUS_HOST="nexus.internal.local"
NEXUS_PORT="8443"

# Check Nexus service
if systemctl is-active --quiet nexus; then
    echo "✓ Nexus service is running"
else
    echo "✗ Nexus service is not running"
    exit 1
fi

# Check HTTP endpoint
if curl -k -s "https://$NEXUS_HOST:$NEXUS_PORT/service/rest/v1/status" > /dev/null; then
    echo "✓ Nexus HTTP endpoint is responsive"
else
    echo "✗ Nexus HTTP endpoint is not responsive"
    exit 1
fi

# Check Docker registry endpoint
if curl -k -s "https://$NEXUS_HOST:5002/v2/" > /dev/null; then
    echo "✓ Docker registry endpoint is responsive"
else
    echo "✗ Docker registry endpoint is not responsive"
    exit 1
fi

# Check disk space
DISK_USAGE=$(df /opt/sonatype-nexus | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -lt 80 ]; then
    echo "✓ Disk usage is acceptable ($DISK_USAGE%)"
else
    echo "⚠ Disk usage is high ($DISK_USAGE%)"
fi

echo "Health check complete"

Log Monitoring

# Monitor Nexus logs for errors
tail -f /opt/sonatype-nexus/sonatype-work/nexus3/log/nexus.log | grep -E "(ERROR|WARN)"

# Monitor Docker pulls
tail -f /opt/sonatype-nexus/sonatype-work/nexus3/log/nexus.log | grep "docker"

Troubleshooting Common Issues

1. Certificate Issues

# Verify certificate is valid
openssl x509 -in /opt/sonatype-nexus/ssl/nexus.crt -text -noout

# Check certificate trust on client
docker info | grep -A 5 "Registry Mirrors"

# Force certificate refresh
sudo systemctl restart docker

2. Storage Issues

# Check blob store health
curl -k -u admin:password "https://nexus.internal.local:8443/service/rest/v1/blobstores"

# Monitor storage usage
du -sh /opt/sonatype-nexus/sonatype-work/nexus3/blobs/*

# Cleanup old images (run cleanup tasks via API)
curl -k -X POST -u admin:password \
  "https://nexus.internal.local:8443/service/rest/v1/tasks/run/now/cleanup.docker"

3. Authentication Problems

# Test registry authentication
curl -k -u admin:password "https://nexus.internal.local:5002/v2/"

# Check Docker authentication
cat ~/.docker/config.json

# Reset Docker credentials
docker logout nexus.internal.local:5002
docker login nexus.internal.local:5002

4. Network Connectivity Issues

# Test port connectivity
telnet nexus.internal.local 5002

# Check firewall rules
sudo iptables -L -n | grep 5002
sudo firewall-cmd --list-ports

# Verify DNS resolution
nslookup nexus.internal.local

Security Considerations

Access Control

# Create read-only role for developers
Roles → Create role
Role ID: docker-readonly
Role Name: Docker Read Only
Privileges:
  - nx-repository-view-docker-*-browse
  - nx-repository-view-docker-*-read

# Create service account for CI/CD
Users → Create local user
ID: docker-service
First Name: Docker
Last Name: Service
Email: docker-[email protected]
Status: Active
Roles: docker-readonly

Network Security

# Restrict access using iptables
sudo iptables -A INPUT -p tcp --dport 5002 -s 10.0.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 5002 -j DROP

# Or use firewalld
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" port protocol="tcp" port="5002" accept'
sudo firewall-cmd --reload

Performance Optimization

JVM Tuning

# Optimize for air-gapped environments with limited resources
cat > /opt/sonatype-nexus/nexus/bin/nexus.vmoptions << EOF
-Xms4g
-Xmx4g
-XX:MaxDirectMemorySize=6g
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:+UseG1GC
-XX:G1HeapRegionSize=32m
-XX:+UseStringDeduplication
-XX:+OptimizeStringConcat
-XX:+UseCompressedOops
-Djava.awt.headless=true
EOF

Storage Optimization

# Enable compression for blob storage
# Add to nexus.properties
nexus.blobstore.s3.useTransferManager=true
nexus.blobstore.compression.enabled=true

Conclusion

Setting up Nexus Repository Manager as a Docker registry proxy in air-gapped environments requires careful planning and attention to detail, but the payoff in terms of security, control, and reliability is significant. The configuration I've outlined here has been battle-tested in production environments handling thousands of image pulls daily.

Key takeaways for success:

  1. Plan your certificate strategy early - Self-signed certificates work fine internally, but ensure they're properly distributed and trusted
  2. Size your storage appropriately - Docker images can consume significant space; plan for growth and implement cleanup policies
  3. Automate image synchronization - Whether through scripts or manual processes, have a clear strategy for keeping your registry current
  4. Monitor actively - Set up health checks and log monitoring to catch issues before they impact developers
  5. Document everything - In air-gapped environments, good documentation is crucial for troubleshooting and maintenance

The investment in properly configuring Nexus for air-gapped Docker registry proxying will pay dividends in developer productivity, security compliance, and operational reliability. Remember to regularly review and update your configuration as your container usage patterns evolve.

Share:

Was this article helpful?

Raafay Asif
Raafay Asif

Linux Systems Engineer

Everything runs on Linux — I make sure it runs well. From kernel tuning to systemd debugging, I live in the terminal. If your server is misbehaving, I've probably seen that exact dmesg output before.

Related Articles

Discussion