Skill flagged — suspicious patterns detected

ClawHub Security flagged this skill as suspicious. Review the scan results before using.

VPS Deploy

v1.0.0

Deploy a full-stack app to any VPS from zero to production in one command. Handles SSH hardening, firewall, Docker, Nginx reverse proxy, SSL certificates, an...

0· 101· 1 versions· 1 current· 1 all-time· Updated 17h ago· MIT-0
bySamih Mansour@llcsamih

Install

openclaw skills install vps-deploy

VPS Deploy

Deploy any application to any VPS — from bare server to production with SSL — in one session.

When to Use

  • User has a VPS (Ubuntu/Debian) and wants to deploy an application
  • User says "deploy to VPS", "set up my server", "go to production"
  • User has a running app locally and wants it live on a server
  • User needs SSL, Nginx, Docker setup on a VPS

When NOT to Use

  • Deploying to Vercel, Netlify, or other managed platforms (use their CLIs)
  • Deploying to Kubernetes (use /k8s-deploy if it exists)
  • The user just wants to push code (use git push)

Prerequisites

  • SSH access to a VPS (IP address + root or sudo credentials)
  • A domain pointed to the VPS IP (for SSL — can skip SSL if no domain)
  • The app must have a Dockerfile or be deployable via Docker

Execution Flow

Phase 1: Gather Information

Ask the user for (skip what you can detect):

  1. VPS IP address and SSH credentials (root password or key path)
  2. Domain name (optional — needed for SSL)
  3. Application type — detect from the current directory if possible:
    • Look for package.json (Node.js/Next.js)
    • Look for requirements.txt / pyproject.toml (Python)
    • Look for go.mod (Go)
    • Look for Dockerfile (any)
  4. Port the app runs on (detect from Dockerfile EXPOSE, or ask)
  5. Environment variables needed (check .env.local, .env.example)
  6. Database requirements (Postgres, MySQL, Redis, MongoDB — detect from dependencies)

Phase 2: Server Setup (SSH into VPS)

Run these via ssh root@<IP> commands. Chain with && for safety.

2a. System Update

apt update && apt upgrade -y

2b. Create Deploy User

adduser --disabled-password --gecos "" deploy
usermod -aG sudo docker deploy
echo "deploy ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/deploy

2c. SSH Hardening

# Copy root's authorized_keys to deploy user
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/ 2>/dev/null || true
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys 2>/dev/null || true

# Harden SSH config
sed -i 's/#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd

CRITICAL: Before disabling root login, verify the deploy user can SSH in. Test in a separate connection. If the user doesn't have SSH keys set up, help them first.

2d. Firewall (UFW)

apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp   # SSH
ufw allow 80/tcp   # HTTP
ufw allow 443/tcp  # HTTPS
ufw --force enable

2e. Install Docker

curl -fsSL https://get.docker.com | sh
usermod -aG docker deploy

2f. Install Docker Compose (v2)

apt install -y docker-compose-plugin
# Verify
docker compose version

Phase 3: Application Deployment

3a. Generate Dockerfile (if none exists)

Detect the stack and generate an appropriate multi-stage Dockerfile:

Node.js / Next.js:

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

Python (FastAPI/Flask):

FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Adapt based on what you detect in the project.

3b. Generate docker-compose.yml

Generate a production-grade compose file. Include:

services:
  app:
    build: .
    restart: unless-stopped
    ports:
      - "127.0.0.1:${APP_PORT:-3000}:${APP_PORT:-3000}"
    env_file: .env
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:${APP_PORT:-3000}/"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  # Add database services based on detection:
  # postgres:
  #   image: postgres:16-alpine
  #   restart: unless-stopped
  #   volumes:
  #     - pgdata:/var/lib/postgresql/data
  #   environment:
  #     POSTGRES_DB: ${DB_NAME:-app}
  #     POSTGRES_USER: ${DB_USER:-app}
  #     POSTGRES_PASSWORD: ${DB_PASSWORD}
  #   healthcheck:
  #     test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-app}"]
  #     interval: 10s
  #     timeout: 5s
  #     retries: 5

# volumes:
#   pgdata:

Key rules:

  • Always bind app port to 127.0.0.1 (Nginx handles external traffic)
  • Always include health checks
  • Always include resource limits
  • Always include logging limits
  • Always use restart: unless-stopped
  • Never expose database ports to the host

3c. Transfer Files to VPS

# Create app directory
ssh deploy@<IP> "mkdir -p ~/apps/<app-name>"

# Copy project files (exclude node_modules, .git, etc.)
rsync -avz --exclude='node_modules' --exclude='.git' --exclude='.next' \
  ./ deploy@<IP>:~/apps/<app-name>/

3d. Build and Start

ssh deploy@<IP> "cd ~/apps/<app-name> && docker compose up -d --build"

Phase 4: Nginx Reverse Proxy

apt install -y nginx

# Generate site config
cat > /etc/nginx/sites-available/<domain> << 'EOF'
server {
    listen 80;
    server_name <domain>;

    location / {
        proxy_pass http://127.0.0.1:<APP_PORT>;
        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;
        proxy_read_timeout 86400;
    }
}
EOF

ln -sf /etc/nginx/sites-available/<domain> /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx

Phase 5: SSL Certificate (Let's Encrypt)

Skip if no domain provided.

apt install -y certbot python3-certbot-nginx
certbot --nginx -d <domain> --non-interactive --agree-tos -m <email>
# Auto-renewal is configured automatically by certbot

Phase 6: Verification

Run these checks and report results:

# Check Docker containers are running
docker compose ps

# Check health status
docker inspect --format='{{.State.Health.Status}}' <container-name>

# Check Nginx is serving
curl -sI https://<domain> | head -5

# Check SSL certificate
echo | openssl s_client -servername <domain> -connect <domain>:443 2>/dev/null | openssl x509 -noout -dates

Report to the user:

  • Container status (running/healthy)
  • HTTP response code
  • SSL expiry date
  • URL to visit

Safety Rules

  • NEVER disable the firewall without asking
  • NEVER expose database ports to the internet
  • ALWAYS verify SSH access with the deploy user BEFORE disabling root login
  • ALWAYS back up existing Nginx configs before overwriting
  • ALWAYS run nginx -t before reloading Nginx
  • ALWAYS ask before running destructive commands (rm, drop, etc.)
  • If anything fails, stop and tell the user what went wrong. Don't chain workarounds.

Troubleshooting

  • Port already in use: lsof -i :<port> to find what's using it
  • Docker build fails: Check Dockerfile, show build logs
  • SSL fails: Verify domain DNS points to VPS IP (dig <domain>)
  • App crashes: docker compose logs -f to show logs
  • Can't SSH as deploy user: Don't disable root login yet, fix keys first

Post-Deploy Checklist

Tell the user to:

  1. Set up monitoring (suggest Uptime Kuma)
  2. Configure automated backups for database volumes
  3. Set up log rotation (already configured in compose)
  4. Consider a CI/CD pipeline (suggest /ci-gen skill)
  5. Add the domain to their DNS if not done

Version tags

latestvk97d3djjc40k7x8jb5mbxqjkbn84e0aa