Files
Web-Page-Performance-Test/PROXMOX_DEPLOY_TEMPLATE.md

14 KiB
Raw Permalink Blame History

🚀 Proxmox Deployment Template (TurnKey Node.js)

Real-world tested deployment guide for ANY Node.js application to TurnKey Linux LXC Container.

This template is based on actual deployment experience and includes all the fixes and troubleshooting steps discovered during real deployments.


📋 Prerequisites

  1. Project: A Node.js application (Express, Next.js, static site, etc.) in a Git repository
  2. Server: A Proxmox TurnKey Node.js Container (already created)
  3. Access: Root SSH password for the container
  4. GitHub: Personal Access Token (PAT) with repo permissions
  5. Domain (Optional): If using Cloudflare Tunnel or DNS

<EFBFBD> Step 0: Prepare GitHub Personal Access Token

  1. Go to: https://github.com/settings/tokens
  2. Click "Generate new token (classic)"
  3. Set note: "Proxmox Auto-Deploy"
  4. Check the repo scope (full control of private repositories)
  5. Click "Generate token"
  6. Copy the token immediately (you won't see it again!)

🛠️ Step 1: Prepare Your Project Locally

1.1 Ensure Port Configuration

Your app should listen on a configurable port:

// server.js
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

1.2 Create .gitignore

CRITICAL: Protect credentials from Git:

# Credentials - NEVER commit these
deploy-config.json
credentials*.json
.env
*.pem
*.key

1.3 Create deploy-config.json (Locally Only)

Create this file locally but NEVER commit it:

{
  "host": "YOUR_SERVER_IP",
  "port": 22,
  "username": "root",
  "password": "YOUR_ROOT_PASSWORD",
  "remotePath": "/var/www/your-app-name",
  "appName": "your-app-name",
  "github": {
    "username": "YourGitHubUsername",
    "token": "ghp_YourPersonalAccessToken",
    "repo": "YourUsername/YourRepo"
  }
}

🖥️ Step 2: Initial Server Setup (SSH)

2.1 SSH into your server

ssh root@YOUR_SERVER_IP

2.2 Install essentials

apt-get update && apt-get install -y git jq

2.3 Verify Node.js installation

which node
node --version
# TurnKey Node.js usually installs to: /usr/local/bin/node

📦 Step 3: Deploy Your Application

3.1 Create application directory

mkdir -p /var/www/your-app-name
cd /var/www/your-app-name

3.2 Clone repository with authentication

CRITICAL: Use HTTPS with token authentication:

# Format: https://USERNAME:TOKEN@github.com/REPO.git
git clone 'https://YourUsername:ghp_YourToken@github.com/YourUsername/YourRepo.git' .

# Remove credentials from Git config immediately
git remote set-url origin "https://github.com/YourUsername/YourRepo.git"

3.3 Configure Git credential helper (memory only)

git config credential.helper 'cache --timeout=3600'

3.4 Install dependencies

npm install --production

⚙️ Step 4: Create Systemd Service

Why Systemd instead of PM2?

  • Native to Linux (no extra software)
  • Better logging with journalctl
  • More reliable auto-restart
  • Boot persistence without configuration

4.1 Create service file

cat > /etc/systemd/system/your-app-name.service << 'EOF'
[Unit]
Description=Your App Name Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/var/www/your-app-name
ExecStart=/usr/local/bin/node server.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=your-app-name

[Install]
WantedBy=multi-user.target
EOF

IMPORTANT: Verify the path to node (/usr/local/bin/node) using which node

4.2 Enable and start service

systemctl daemon-reload
systemctl enable your-app-name
systemctl start your-app-name

# Check status
systemctl status your-app-name --no-pager

Troubleshooting: If you get status=203/EXEC, verify the ExecStart path is correct.


🌐 Step 5: Configure Nginx (Critical for TurnKey)

5.1 CRITICAL: Remove TurnKey Default Sites

TurnKey Linux comes with default Nginx configurations that show the TurnKey control panel. You MUST remove these:

# Remove ALL TurnKey default sites
rm -f /etc/nginx/sites-enabled/default
rm -f /etc/nginx/sites-enabled/nodejs
rm -f /etc/nginx/sites-enabled/node*
rm -f /etc/nginx/sites-enabled/tkl-webcp

5.2 Create your application's Nginx configuration

IMPORTANT: Watch variable escaping! Use single quotes or backticks properly:

cat > /etc/nginx/sites-available/your-app-name << 'EOF'
server {
    listen 80 default_server;
    server_name _;

    # Serve static files directly (faster!)
    root /var/www/your-app-name;
    index index.html;

    # Try static files first, then proxy to Node.js
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Proxy API requests to Node.js
    location /api {
        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_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;
    }

    # 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;

    # Cache static assets
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}
EOF

5.3 Enable your site

# Link your site configuration
ln -sf /etc/nginx/sites-available/your-app-name /etc/nginx/sites-enabled/

# Test configuration
nginx -t

# Reload Nginx
systemctl reload nginx

5.4 Verify it's working

# Test locally
curl -I http://localhost

# Should show: HTTP/1.1 200 OK
# If you see "TurnKey Node.js" in the output, the old config is still active

🔄 Step 6: Setup Auto-Sync (Every 60 Seconds)

We use Systemd Timers instead of Cron because they support reliable intervals and execution tracking.

6.1 Create robust auto-sync script (with file locking)

# ... (Script content same as before, ensures single instance)

6.2 Create Systemd Timer Units

Service Unit (/etc/systemd/system/your-app-name-sync.service):

[Unit]
Description=Auto-sync Service
[Service]
Type=oneshot
ExecStart=/var/www/your-app-name/auto-sync.sh
User=root

Timer Unit (/etc/systemd/system/your-app-name-sync.timer):

[Unit]
Description=Run auto-sync every 60 seconds

[Timer]
OnBootSec=1min
OnUnitActiveSec=60s
Unit=your-app-name-sync.service

[Install]
WantedBy=timers.target

💡 Pro Tip: Fix SSH Connection Limits If you get "Connection refused" errors during deployment, reliable automation requires higher SSH limits. Update /etc/ssh/sshd_config:

MaxStartups 100:30:200
MaxSessions 100

Then run systemctl restart ssh.

6.3 Enable the Timer

systemctl daemon-reload
systemctl enable --now your-app-name-sync.timer
systemctl list-timers --all

🧪 Step 7: Verify Deployment

7.1 Check service status

systemctl status your-app-name --no-pager
# Should show: active (running)

7.2 Check Nginx

systemctl status nginx
nginx -t

7.3 Test the application

# Test locally
curl http://localhost

# Should return your HTML, NOT the TurnKey control panel

7.4 Test from browser

Visit: http://YOUR_SERVER_IP

If you see the TurnKey control panel, go back to Step 5 and verify you removed all default Nginx sites.


🚨 Common Issues & Fixes

Issue 1: TurnKey Control Panel Shows Instead of App

Symptoms: Browser shows "TurnKey Node.js" page with Webmin link

Cause: TurnKey default Nginx configuration is still active

Fix:

# Remove ALL default sites
rm -f /etc/nginx/sites-enabled/default
rm -f /etc/nginx/sites-enabled/nodejs
rm -f /etc/nginx/sites-enabled/node*
rm -f /etc/nginx/sites-enabled/tkl-webcp

# Verify only your site is enabled
ls -la /etc/nginx/sites-enabled/
# Should only show: your-app-name

# Reload Nginx
nginx -t && systemctl reload nginx

Issue 2: Nginx Shows "500 Internal Server Error"

Cause: Variable escaping issues in Nginx config

Fix: Recreate the Nginx config using the exact format in Step 5.2

# View your current config
cat /etc/nginx/sites-enabled/your-app-name

# If you see \\$uri instead of $uri, the escaping is wrong
# Delete and recreate using Step 5.2

Issue 3: Service Keeps Restarting (status=203/EXEC)

Cause: Wrong path to node executable

Fix:

# Find correct node path
which node

# Update service file with correct path
sudo nano /etc/systemd/system/your-app-name.service
# Change ExecStart to match the output of 'which node'

# Reload
systemctl daemon-reload
systemctl restart your-app-name

Issue 4: Git Clone Fails with "already exists"

Cause: Directory not empty

Fix:

cd /var/www
rm -rf your-app-name
mkdir -p your-app-name
cd your-app-name
# Then retry git clone

Issue 5: Auto-Sync Not Working

Check:

# Verify cron job exists
crontab -l | grep auto-sync

# Manually run sync to see errors
cd /var/www/your-app-name
./auto-sync.sh

# Check logs
tail -f /var/log/your-app-name-autosync.log

📊 Useful Commands

Service Management

# Status
systemctl status your-app-name

# Restart
systemctl restart your-app-name

# View logs
journalctl -u your-app-name -f

# View last 50 lines
journalctl -u your-app-name -n 50

Nginx Commands

# Test configuration
nginx -t

# Reload
systemctl reload nginx

# Restart
systemctl restart nginx

# View error logs
tail -f /var/log/nginx/error.log

# View access logs
tail -f /var/log/nginx/access.log

Auto-Sync Commands

# View auto-sync logs
tail -f /var/log/your-app-name-autosync.log

# Manually trigger sync
cd /var/www/your-app-name && ./auto-sync.sh

# Check cron jobs
crontab -l

Git Commands

# Check current commit
git log -1 --oneline

# Check remote
git remote -v

# Force pull
git fetch origin main
git reset --hard origin/main

Deployment Checklist

Use this checklist for every deployment:

  • SSH key accepted (run ssh root@IP manually first)
  • GitHub PAT created and copied
  • .gitignore includes deploy-config.json
  • Local deploy-config.json created with credentials
  • Repository cloned with token authentication
  • Dependencies installed (npm install)
  • Systemd service created with correct node path
  • Service started and active
  • ALL TurnKey Nginx defaults removed
  • Your Nginx config created and enabled
  • Nginx reloaded successfully
  • Browser shows YOUR app (not TurnKey page)
  • Auto-sync script created and executable
  • Cron job added and verified
  • Test auto-sync by pushing a change

🎯 Quick Deployment Commands (Copy-Paste)

Replace YOUR_APP_NAME, YOUR_REPO, etc. with your values:

# 1. Install essentials
apt-get update && apt-get install -y git jq

# 2. Clone and setup
mkdir -p /var/www/YOUR_APP_NAME
cd /var/www/YOUR_APP_NAME
git clone 'https://USERNAME:TOKEN@github.com/USER/REPO.git' .
git remote set-url origin "https://github.com/USER/REPO.git"
npm install --production

# 3. Create systemd service (update node path!)
cat > /etc/systemd/system/YOUR_APP_NAME.service << 'EOF'
[Unit]
Description=YOUR_APP_NAME Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/var/www/YOUR_APP_NAME
ExecStart=/usr/local/bin/node server.js
Restart=always
Environment=NODE_ENV=production
Environment=PORT=3000
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload && systemctl enable YOUR_APP_NAME && systemctl start YOUR_APP_NAME

# 4. Remove TurnKey defaults (CRITICAL!)
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/nodejs /etc/nginx/sites-enabled/node* /etc/nginx/sites-enabled/tkl-webcp

# 5. Create Nginx config
cat > /etc/nginx/sites-available/YOUR_APP_NAME << 'EOF'
server {
    listen 80 default_server;
    server_name _;
    root /var/www/YOUR_APP_NAME;
    index index.html;
    location / { try_files $uri $uri/ /index.html; }
    location /api {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }
}
EOF

ln -sf /etc/nginx/sites-available/YOUR_APP_NAME /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

# 6. Test
curl -I http://localhost

🎉 Success Indicators

You'll know deployment succeeded when:

systemctl status your-app-name shows active (running)
curl http://localhost returns your HTML (not TurnKey page)
Browser at http://SERVER_IP shows your application
Auto-sync logs show successful checks
Pushing to GitHub triggers auto-update within 5 minutes


📚 Additional Resources


Last Updated: December 2025 (Based on real deployment experience)

Tested On: TurnKey Linux Node.js 18.x LXC Container on Proxmox VE 8.x