Deployment Methods
Registry Server supports multiple deployment methods. You can choose the most suitable solution based on your actual needs.
Method Comparison
| Deployment Method | Use Case | Advantages |
|---|---|---|
| Local Development | Development and testing | Quick start, easy to debug |
| PM2 | Small production | Simple to use, process manager |
| Docker | Medium to large production | Good isolation, easy to migrate |
Local Development Deployment
Suitable for development and quick testing scenarios.
Development Mode
# Navigate to project directory
cd apps/registry-server
# Start development server (watch mode)
pnpm devDevelopment mode automatically watches for code changes and restarts the service.
Production Mode
# Build project
pnpm build
# Start production server
pnpm startNot 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
# Install PM2 globally
npm install -g pm2Method 1: Direct Start
# 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-serverMethod 2: Using Configuration File (Recommended)
Create PM2 configuration file apps/registry-server/ecosystem.config.js:
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:
{
instances: 1,
exec_mode: 'fork'
}Start using configuration file:
# Start service
pm2 start ecosystem.config.js
# Reload configuration
pm2 reload ecosystem.config.js
# Delete application
pm2 delete registry-serverAuto-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:
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
# Build image
docker build -t registry-server:latest .
# View image
docker images | grep registry-serverRun Container
# 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-serverUsing Docker Compose (Recommended)
Create docker-compose.yml:
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:
# 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 --buildNginx Reverse Proxy
In production environments, Nginx is typically used as a reverse proxy to provide HTTPS support and load balancing.
Install Nginx
# Ubuntu/Debian
sudo apt-get install -y nginx
# CentOS/RHEL
sudo yum install -y nginxConfigure Nginx
Create configuration file /etc/nginx/sites-available/registry:
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:
# 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 nginxObtain SSL Certificate
Using Let's Encrypt free certificate:
# 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
