Skip to Content
Browse all templates at capricorn.build →

Self-Hosted Deployment

Deploy your Next.js template on your own infrastructure. Full control over your hosting environment.

Why Self-Host?

Full Control - Complete infrastructure control ✅ Data Privacy - Keep data on your servers ✅ Custom Configuration - Unlimited customization ✅ Cost Control - Potentially lower costs at scale ✅ No Vendor Lock-in - Use any hosting provider

Prerequisites

  • Linux server (Ubuntu 20.04+ recommended)
  • Node.js 18.17 or later
  • Domain name
  • Basic server administration knowledge

Deployment Methods

Docker provides consistent, portable deployments.

Step 1: Create Dockerfile

# Dockerfile FROM node:18-alpine AS base # Install dependencies only when needed FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci # Rebuild source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Set environment variables ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build # Production image FROM base AS runner WORKDIR /app ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 ENV PORT 3000 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"]

Step 2: Update next.config.js

/** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', // ... your other config } module.exports = nextConfig

Step 3: Create docker-compose.yml

version: '3.8' services: web: build: . ports: - "3000:3000" environment: - NODE_ENV=production - NEXT_PUBLIC_SITE_URL=https://yourdomain.com restart: unless-stopped

Step 4: Build and Run

# Build image docker build -t my-nextjs-app . # Run container docker run -p 3000:3000 my-nextjs-app # Or use docker-compose docker-compose up -d

Method 2: PM2 (Node.js Process Manager)

PM2 keeps your app running and handles crashes.

Step 1: Install PM2

npm install -g pm2

Step 2: Build Application

npm run build

Step 3: Create ecosystem.config.js

module.exports = { apps: [{ name: 'nextjs-app', script: 'npm', args: 'start', cwd: '/path/to/your/app', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, NEXT_PUBLIC_SITE_URL: 'https://yourdomain.com' } }] }

Step 4: Start with PM2

# Start app pm2 start ecosystem.config.js # Save PM2 config pm2 save # Set PM2 to start on boot pm2 startup

PM2 Commands:

# Status pm2 status # Logs pm2 logs # Restart pm2 restart nextjs-app # Stop pm2 stop nextjs-app # Delete pm2 delete nextjs-app

Method 3: Systemd Service

Create a systemd service for automatic startup.

Step 1: Create Service File

sudo nano /etc/systemd/system/nextjs-app.service
[Unit] Description=Next.js Application After=network.target [Service] Type=simple User=www-data WorkingDirectory=/var/www/nextjs-app ExecStart=/usr/bin/npm start Restart=on-failure Environment=NODE_ENV=production Environment=PORT=3000 [Install] WantedBy=multi-user.target

Step 2: Enable and Start

# Reload systemd sudo systemctl daemon-reload # Enable on boot sudo systemctl enable nextjs-app # Start service sudo systemctl start nextjs-app # Check status sudo systemctl status nextjs-app

Nginx Configuration

Use Nginx as reverse proxy for better performance and SSL.

Step 1: Install Nginx

sudo apt update sudo apt install nginx

Step 2: Configure Nginx

Create config file:

sudo nano /etc/nginx/sites-available/nextjs-app
# Basic configuration server { listen 80; server_name yourdomain.com www.yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; 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; } # Optimize static files location /_next/static { proxy_pass http://localhost:3000; proxy_cache_valid 60m; add_header Cache-Control "public, max-age=3600, immutable"; } # Optimize images location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { proxy_pass http://localhost:3000; proxy_cache_valid 60m; add_header Cache-Control "public, max-age=3600"; } }

Step 3: Enable Site

# Create symlink sudo ln -s /etc/nginx/sites-available/nextjs-app /etc/nginx/sites-enabled/ # Test configuration sudo nginx -t # Restart Nginx sudo systemctl restart nginx

SSL/HTTPS with Let’s Encrypt

Step 1: Install Certbot

sudo apt install certbot python3-certbot-nginx

Step 2: Obtain Certificate

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Step 3: Auto-renewal

Certbot automatically sets up renewal. Test it:

sudo certbot renew --dry-run

Updated Nginx Config (HTTPS):

server { listen 80; server_name yourdomain.com www.yourdomain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name yourdomain.com www.yourdomain.com; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; # SSL configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }

Environment Variables

Method 1: .env File

# .env.production NODE_ENV=production PORT=3000 NEXT_PUBLIC_SITE_URL=https://yourdomain.com NEXT_PUBLIC_API_URL=https://api.yourdomain.com

Load with:

export $(cat .env.production | xargs) npm start

Method 2: In Process Manager

For PM2 (in ecosystem.config.js):

env: { NODE_ENV: 'production', PORT: 3000 }

For systemd (in service file):

Environment=NODE_ENV=production Environment=PORT=3000

Monitoring

Server Monitoring

Install monitoring tools:

# htop for system resources sudo apt install htop # Monitor in real-time htop

Application Logs

PM2 logs:

pm2 logs

Systemd logs:

sudo journalctl -u nextjs-app -f

Nginx logs:

# Access log sudo tail -f /var/log/nginx/access.log # Error log sudo tail -f /var/log/nginx/error.log

Continuous Deployment

Basic Git Deployment Script

deploy.sh:

#!/bin/bash # Navigate to app directory cd /var/www/nextjs-app # Pull latest changes git pull origin main # Install dependencies npm ci # Build application npm run build # Restart application pm2 restart nextjs-app # Or restart systemd service # sudo systemctl restart nextjs-app echo "Deployment complete!"

Make executable:

chmod +x deploy.sh

Automated Deployment with GitHub Actions

.github/workflows/deploy.yml:

name: Deploy to Server on: push: branches: [ main ] jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy to server uses: appleboy/ssh-action@master with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/nextjs-app git pull origin main npm ci npm run build pm2 restart nextjs-app

Security Best Practices

1. Firewall Configuration

# Enable UFW sudo ufw enable # Allow SSH sudo ufw allow ssh # Allow HTTP/HTTPS sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Check status sudo ufw status

2. Keep System Updated

# Update packages sudo apt update && sudo apt upgrade -y # Auto-updates (optional) sudo apt install unattended-upgrades

3. Limit SSH Access

Edit /etc/ssh/sshd_config:

PermitRootLogin no PasswordAuthentication no

Restart SSH:

sudo systemctl restart ssh

4. Use Environment Variables

Never commit secrets:

# .gitignore .env* !.env.example

Backup Strategy

Database Backups

# Backup script #!/bin/bash BACKUP_DIR="/var/backups/nextjs-app" DATE=$(date +%Y%m%d_%H%M%S) # Create backup tar -czf $BACKUP_DIR/backup_$DATE.tar.gz /var/www/nextjs-app # Keep only last 7 days find $BACKUP_DIR -name "backup_*.tar.gz" -mtime +7 -delete

Automated Backups

Add to crontab:

crontab -e # Daily backup at 2 AM 0 2 * * * /path/to/backup-script.sh

Performance Optimization

Enable Caching

Nginx caching:

# Cache configuration proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=nextjs_cache:10m max_size=1g inactive=60m; location / { proxy_cache nextjs_cache; proxy_cache_valid 200 60m; proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504; }

Enable Compression

# Gzip compression gzip on; gzip_vary on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml;

Troubleshooting

Application Won’t Start

# Check Node version node --version # Check build npm run build # Check logs pm2 logs # or sudo journalctl -u nextjs-app -n 50

Port Already in Use

# Find process using port sudo lsof -i :3000 # Kill process kill -9 <PID>

Nginx Issues

# Test configuration sudo nginx -t # Check error logs sudo tail -f /var/log/nginx/error.log # Restart Nginx sudo systemctl restart nginx

SSL Certificate Issues

# Renew manually sudo certbot renew # Check certificate status sudo certbot certificates

Scaling Considerations

Load Balancing

For multiple servers, use Nginx load balancing:

upstream nextjs_backend { server 127.0.0.1:3000; server 127.0.0.1:3001; server 127.0.0.1:3002; } server { location / { proxy_pass http://nextjs_backend; } }

CDN Integration

Use CDN for static assets:

  1. Upload public/ and .next/static/ to CDN
  2. Update asset URLs in configuration

Cost Estimation

Basic VPS:

  • 1GB RAM, 1 CPU: $5-10/month
  • 2GB RAM, 2 CPU: $10-20/month
  • 4GB RAM, 2 CPU: $20-40/month

Additional Costs:

  • Domain: $10-15/year
  • SSL: Free (Let’s Encrypt)
  • Backups: $1-5/month
  • Monitoring: Free-$10/month

Next Steps

Deployed? Now:

  1. Set up monitoring
  2. Configure automated backups
  3. Enable SSL/HTTPS
  4. Set up firewall
  5. Configure continuous deployment
  6. Monitor logs regularly

Need help? Email support@capricorn.engineering


Resources:

Last updated on