Skip to content

Deployment Methods

Registry Server supports multiple deployment methods. You can choose the most suitable solution based on your actual needs.

Method Comparison

Deployment MethodUse CaseAdvantages
Local DevelopmentDevelopment and testingQuick start, easy to debug
PM2Small productionSimple to use, process manager
DockerMedium to large productionGood isolation, easy to migrate

Local Development Deployment

Suitable for development and quick testing scenarios.

Development Mode

bash
# Navigate to project directory
cd apps/registry-server

# Start development server (watch mode)
pnpm dev

Development mode automatically watches for code changes and restarts the service.

Production Mode

bash
# Build project
pnpm build

# Start production server
pnpm start

Not Recommended for Production

Running locally directly lacks process supervision and auto-restart capabilities, only suitable for development and testing.

PM2 Deployment

PM2 is a powerful Node.js process manager, suitable for single-server production deployment.

Install PM2

bash
# Install PM2 globally
npm install -g pm2

Method 1: Direct Start

bash
# Build project
pnpm --filter @rack/registry-server build

# Start with PM2
cd apps/registry-server
pm2 start dist/server.js --name registry-server

# Check status
pm2 status

# View logs
pm2 logs registry-server

# Stop service
pm2 stop registry-server

# Restart service
pm2 restart registry-server

Create PM2 configuration file apps/registry-server/ecosystem.config.js:

javascript
module.exports = {
  apps: [
    {
      name: 'registry-server',
      script: './dist/server.js',
      instances: 2, // Number of instances (utilize multi-core CPU)
      exec_mode: 'cluster', // Cluster mode
      env: {
        NODE_ENV: 'production',
        PORT: 8080,
        HOST: '0.0.0.0'
      },
      error_file: './logs/error.log',
      out_file: './logs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      autorestart: true, // Auto restart
      max_memory_restart: '1G', // Auto restart when memory exceeds 1G
      watch: false // Disable file watching in production
    }
  ]
}

Cluster Mode and Webhooks

The Webhook service uses an in-memory queue. In cluster mode, each Worker process maintains its own independent queue. This means a single upload event will trigger Webhooks once per Worker — causing duplicate notifications.

If you use Webhooks, set instances: 1 or use fork mode (exec_mode: 'fork') to avoid this:

javascript
{
  instances: 1,
  exec_mode: 'fork'
}

Start using configuration file:

bash
# Start service
pm2 start ecosystem.config.js

# Reload configuration
pm2 reload ecosystem.config.js

# Delete application
pm2 delete registry-server

Auto-start on Boot

Run pm2 save && pm2 startup and follow the command PM2 prints (usually requires sudo). For day-to-day operations (pm2 list / show / logs / monit / flush / restart all), see the PM2 docs.

Docker Deployment

Docker is the recommended production deployment method, providing good isolation and portability.

Create Dockerfile

Create a Dockerfile in the project root:

dockerfile
FROM node:22-alpine AS builder

# Install pnpm
RUN corepack enable pnpm

WORKDIR /app

# Copy dependency files
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/registry-server/package.json ./apps/registry-server/
COPY packages/storage/package.json ./packages/storage/

# Install dependencies
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build project
RUN pnpm --filter @rack/registry-server build

# Production image
FROM node:22-alpine

# Install pnpm
RUN corepack enable pnpm

WORKDIR /app

# Copy dependency files
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/registry-server/package.json ./apps/registry-server/

# Install production dependencies only
RUN pnpm install --frozen-lockfile --prod

# Copy artifacts from builder stage
COPY --from=builder /app/apps/registry-server/dist ./apps/registry-server/dist
COPY --from=builder /app/packages/storage ./packages/storage
COPY apps/registry-server/config ./apps/registry-server/config

# Set environment variables
ENV NODE_ENV=production
ENV PORT=8080
ENV HOST=0.0.0.0

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:8080/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

# Start service
CMD ["node", "apps/registry-server/dist/server.js"]

Build Image

bash
# Build image
docker build -t registry-server:latest .

# View image
docker images | grep registry-server

Run Container

bash
# Basic run
docker run -d \
  --name registry-server \
  -p 8080:8080 \
  registry-server:latest

# Mount storage directory and config
docker run -d \
  --name registry-server \
  -p 8080:8080 \
  -v $(pwd)/storage:/app/packages/storage \
  -v $(pwd)/config:/app/apps/registry-server/config:ro \
  -e NODE_ENV=production \
  -e PORT=8080 \
  --restart unless-stopped \
  registry-server:latest

# View logs
docker logs -f registry-server

# Stop container
docker stop registry-server

# Remove container
docker rm registry-server

Create docker-compose.yml:

yaml
version: '3.8'

services:
  registry-server:
    build: .
    container_name: registry-server
    ports:
      - '8080:8080'
    volumes:
      - ./storage:/app/packages/storage
      - ./apps/registry-server/config:/app/apps/registry-server/config:ro
      - registry-logs:/app/apps/registry-server/logs
    environment:
      - NODE_ENV=production
      - PORT=8080
      - HOST=0.0.0.0
      - LOG_LEVEL=info
    restart: unless-stopped
    healthcheck:
      test:
        [
          'CMD',
          'node',
          '-e',
          "require('http').get('http://localhost:8080/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
        ]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s

volumes:
  registry-logs:

Using Docker Compose:

bash
# Start service
docker-compose up -d

# View logs
docker-compose logs -f

# Restart service
docker-compose restart

# Stop service
docker-compose down

# Rebuild and start
docker-compose up -d --build

Nginx Reverse Proxy

In production environments, Nginx is typically used as a reverse proxy to provide HTTPS support and load balancing.

Install Nginx

bash
# Ubuntu/Debian
sudo apt-get install -y nginx

# CentOS/RHEL
sudo yum install -y nginx

Configure Nginx

Create configuration file /etc/nginx/sites-available/registry:

nginx
upstream registry_backend {
    # If using PM2 cluster mode, can configure multiple backends
    server 127.0.0.1:8080;
    # server 127.0.0.1:8081;
    # server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name registry.example.com;

    # Force HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name registry.example.com;

    # SSL certificate configuration
    ssl_certificate /etc/nginx/ssl/registry.crt;
    ssl_certificate_key /etc/nginx/ssl/registry.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Logging
    access_log /var/log/nginx/registry-access.log;
    error_log /var/log/nginx/registry-error.log;

    # Upload size limit (Registry packages can be large)
    client_max_body_size 100M;

    # Proxy configuration
    location / {
        proxy_pass http://registry_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;

        # Timeout configuration
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Cache static resources
    location ~* \.(json|js|css|png|jpg|jpeg|gif|ico)$ {
        proxy_pass http://registry_backend;
        proxy_cache_valid 200 1h;
        add_header Cache-Control "public, max-age=3600";
    }

    # Health check
    location /health {
        proxy_pass http://registry_backend;
        access_log off;
    }
}

Enable configuration:

bash
# Create symbolic link
sudo ln -s /etc/nginx/sites-available/registry /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Restart Nginx
sudo systemctl restart nginx

Obtain SSL Certificate

Using Let's Encrypt free certificate:

bash
# Install Certbot
sudo apt-get install -y certbot python3-certbot-nginx

# Obtain certificate
sudo certbot --nginx -d registry.example.com

# Auto renewal (Certbot automatically sets up cron job)
sudo certbot renew --dry-run

Released under the MIT License.