Self-hosting ZapDev gives you complete control over your deployment environment. This guide covers deploying to VPS providers, on-premises servers, and custom infrastructure.
Self-hosting requires more configuration than Vercel but provides maximum flexibility and control.
Prerequisites
Linux server (Ubuntu 22.04+ recommended)
Node.js 18+ or Bun runtime
Nginx or Caddy for reverse proxy
SSL certificate (Let’s Encrypt recommended)
2GB+ RAM, 2+ CPU cores
All required service accounts (Convex, Clerk, E2B, etc.)
Server Providers
Recommended VPS providers:
DigitalOcean - Droplets starting at $12/month
Linode/Akamai - Shared CPU instances
Vultr - Cloud compute instances
Hetzner - Cost-effective European hosting
AWS Lightsail - Simple AWS alternative
On-premises - Your own hardware
Initial Server Setup
Create Server Instance
Create a new instance with:
OS : Ubuntu 22.04 LTS
RAM : 2GB minimum (4GB recommended)
CPU : 2 cores minimum
Storage : 25GB minimum
Region : Closest to your users
Update System
apt update && apt upgrade -y
Create Non-Root User
# Create user
adduser zapdev
# Add to sudo group
usermod -aG sudo zapdev
# Switch to new user
su - zapdev
Install Dependencies
Install Bun
curl -fsSL https://bun.sh/install | bash
# Add to PATH
echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Verify installation
bun --version
Install Nginx
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx
Install Certbot (SSL)
sudo apt install certbot python3-certbot-nginx -y
Deploy Application
Clone Repository
cd ~
git clone https://github.com/your-username/zapdev.git
cd zapdev
Create Environment File
cp env.example .env.production
nano .env.production
Add all required environment variables (see Environment Configuration below).
Build Application
# Set environment
export NODE_ENV = production
# Build
bun run build
Environment Configuration
Edit .env.production with your values:
# Application
NEXT_PUBLIC_APP_URL = "https://your-domain.com"
# Convex Database
NEXT_PUBLIC_CONVEX_URL = "https://your-deployment.convex.cloud"
NEXT_PUBLIC_CONVEX_SITE_URL = "https://your-domain.com"
# Authentication
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = "pk_live_..."
CLERK_SECRET_KEY = "sk_live_..."
CLERK_JWT_ISSUER_DOMAIN = "clerk.your-domain.com"
CLERK_JWT_TEMPLATE_NAME = "convex"
# AI Services
OPENROUTER_API_KEY = "sk-or-..."
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
CEREBRAS_API_KEY = "csk-..."
VERCEL_AI_GATEWAY_API_KEY = "..."
# E2B Sandboxes
E2B_API_KEY = "e2b_..."
# Inngest Background Jobs
INNGEST_EVENT_KEY = "ac9_..."
INNGEST_SIGNING_KEY = "signkey-..."
# Polar.sh Billing
POLAR_ACCESS_TOKEN = "polar_at_..."
POLAR_WEBHOOK_SECRET = "whsec_..."
NEXT_PUBLIC_POLAR_ORGANIZATION_ID = "org_..."
NEXT_PUBLIC_POLAR_PRO_PRODUCT_ID = "prod_..."
NEXT_PUBLIC_POLAR_PRO_PRICE_ID = "price_..."
NEXT_PUBLIC_POLAR_SERVER = "production"
# Optional Services
BRAVE_SEARCH_API_KEY = "..."
FIRECRAWL_API_KEY = "..."
UPLOADTHING_TOKEN = "..."
NEXT_PUBLIC_SENTRY_DSN = "..."
SENTRY_DSN = "..."
Process Management with PM2
Use PM2 to keep the application running:
Create PM2 Ecosystem File
module . exports = {
apps: [{
name: 'zapdev' ,
script: 'bun' ,
args: 'run start' ,
cwd: '/home/zapdev/zapdev' ,
env: {
NODE_ENV: 'production' ,
PORT: 3000
},
env_file: '.env.production' ,
instances: 'max' ,
exec_mode: 'cluster' ,
max_memory_restart: '2G' ,
error_file: './logs/error.log' ,
out_file: './logs/out.log' ,
log_file: './logs/combined.log' ,
time: true ,
autorestart: true ,
watch: false
}]
};
Start Application
# Create logs directory
mkdir -p logs
# Start with PM2
pm2 start ecosystem.config.js
# Save PM2 configuration
pm2 save
# Enable PM2 startup on boot
pm2 startup
# Run the command PM2 outputs
Verify Application
# Check status
pm2 status
# View logs
pm2 logs zapdev
# Test locally
curl http://localhost:3000
Create Nginx Configuration
sudo nano /etc/nginx/sites-available/zapdev
# Rate limiting
limit_req_zone $ binary_remote_addr zone=zapdev_limit:10m rate=10r/s;
# Upstream to Next.js
upstream zapdev_backend {
server 127.0.0.1:3000;
keepalive 64 ;
}
server {
listen 80 ;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
# Redirect HTTP to HTTPS (will be configured by Certbot)
return 301 https://$ server_name $ request_uri ;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL certificates (managed by Certbot)
# ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on ;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Logs
access_log /var/log/nginx/zapdev.access.log;
error_log /var/log/nginx/zapdev.error.log;
# Max upload size
client_max_body_size 10M ;
# Rate limiting
limit_req zone=zapdev_limit burst=20 nodelay;
location / {
proxy_pass http://zapdev_backend;
proxy_http_version 1.1 ;
# Proxy headers
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 ;
# Timeouts
proxy_connect_timeout 60s ;
proxy_send_timeout 60s ;
proxy_read_timeout 60s ;
}
# Static files caching
location /_next/static {
proxy_pass http://zapdev_backend;
expires 365d ;
add_header Cache-Control "public, immutable" ;
}
# Public assets caching
location /public {
proxy_pass http://zapdev_backend;
expires 30d ;
add_header Cache-Control "public" ;
}
}
Enable Configuration
# Create symbolic link
sudo ln -s /etc/nginx/sites-available/zapdev /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Configure SSL with Let's Encrypt
# Obtain certificate
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
# Follow prompts to:
# 1. Enter email
# 2. Agree to terms
# 3. Choose redirect option (recommended)
# Test auto-renewal
sudo certbot renew --dry-run
Required Backend Setup
Before your self-hosted deployment works:
Deploy Convex Backend
# Install Convex CLI
bun add -g convex
# Login
bunx convex login
# Deploy
bunx convex deploy
Copy the NEXT_PUBLIC_CONVEX_URL to your .env.production file.
Build E2B Template
# Install E2B CLI
bun add -g @e2b/cli
# Login
e2b auth login
# Build template
cd sandbox-templates/nextjs
e2b template build --name zapdev-production --cmd "/compile_page.sh"
Update the template name in src/inngest/functions.ts before building.
Sync Inngest Cloud
Go to Inngest Dashboard
Click “Sync App”
Add URL: https://your-domain.com/api/inngest
Click “Sync”
Configure Webhooks
Update webhook URLs: Clerk : Dashboard → Webhooks → https://your-domain.com/api/webhooks/clerkPolar.sh : Dashboard → Webhooks → https://your-domain.com/api/webhooks/polar
Firewall Configuration
Enable UFW
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Verify Rules
Should show:
22/tcp (SSH)
80/tcp (HTTP)
443/tcp (HTTPS)
Monitoring and Maintenance
Application Monitoring
# PM2 monitoring
pm2 monit
# Check logs
pm2 logs zapdev --lines 100
# Restart application
pm2 restart zapdev
# View process info
pm2 info zapdev
System Monitoring
# Disk usage
df -h
# Memory usage
free -h
# CPU usage
top
# Network connections
ss -tulpn
Log Management
# Nginx logs
sudo tail -f /var/log/nginx/zapdev.access.log
sudo tail -f /var/log/nginx/zapdev.error.log
# Application logs
cd ~/zapdev
tail -f logs/combined.log
# Rotate logs with logrotate
sudo nano /etc/logrotate.d/zapdev
Add:
/home/zapdev/zapdev/logs/*.log {
daily
rotate 14
compress
delaycompress
notifempty
create 0640 zapdev zapdev
sharedscripts
postrotate
pm2 reloadLogs
endscript
}
Automated Backups
Backup Script
# Create backup script
nano ~/backup-zapdev.sh
#!/bin/bash
DATE = $( date +%Y%m%d_%H%M%S )
BACKUP_DIR = " $HOME /backups"
APP_DIR = " $HOME /zapdev"
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup application files
tar -czf $BACKUP_DIR /zapdev_ $DATE .tar.gz \
-C $APP_DIR \
--exclude=node_modules \
--exclude=.next \
--exclude=logs \
.
# Keep only last 7 backups
ls -t $BACKUP_DIR /zapdev_ * .tar.gz | tail -n +8 | xargs rm -f
echo "Backup completed: zapdev_ $DATE .tar.gz"
# Make executable
chmod +x ~/backup-zapdev.sh
# Add to crontab (daily at 2 AM)
crontab -e
Add:
0 2 * * * /home/zapdev/backup-zapdev.sh >> /home/zapdev/backup.log 2>&1
Updating Your Deployment
Pull Latest Changes
cd ~/zapdev
git pull origin main
Restart with PM2
pm2 restart zapdev
# Or reload with zero downtime
pm2 reload zapdev
Update Convex (if schema changed)
Troubleshooting
Application Won’t Start
Check PM2 logs :
Common issues :
Missing environment variables in .env.production
Port 3000 already in use
Invalid Convex URL
502 Bad Gateway
Check if app is running :
pm2 status
curl http://localhost:3000
Check Nginx error logs :
sudo tail -f /var/log/nginx/zapdev.error.log
SSL Certificate Issues
Renew certificate manually :
sudo certbot renew --force-renewal
sudo systemctl reload nginx
High Memory Usage
Restart PM2 process :
Reduce PM2 instances :
// ecosystem.config.js
instances : 2 , // Instead of 'max'
Security Best Practices
Keep System Updated
sudo apt update && sudo apt upgrade -y
Configure SSH Key Auth
# Disable password authentication
sudo nano /etc/ssh/sshd_config
# Set: PasswordAuthentication no
sudo systemctl restart sshd
Install Fail2Ban
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Enable Automatic Security Updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
Production Checklist
Next Steps
Vercel Deployment Simpler deployment with managed infrastructure
Docker Deployment Containerized deployment for flexibility