From forge-cli
Debug and manage Laravel applications in production via Laravel Forge CLI and direct SSH. Activates when the user mentions production logs, debug production, forge, check production, run on server, production database, deploy, SSH to production, server logs, remote artisan, production error, or tinker production.
npx claudepluginhub thecrazybob/claude-code-plugins --plugin forge-cliThis skill uses the workspace's default tool permissions.
Laravel Forge CLI allows managing Forge-provisioned servers from the command line. Use it for debugging production issues, checking logs, and running safe read-only commands.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Laravel Forge CLI allows managing Forge-provisioned servers from the command line. Use it for debugging production issues, checking logs, and running safe read-only commands.
# Install globally
composer global require laravel/forge-cli
# Authenticate (opens browser)
forge login
# List available servers/sites
forge server:list
forge site:list
Forge CLI triggers deprecation warnings with PHP 8.4. Create an alias to suppress them:
# Add to ~/.zshrc or ~/.bashrc
alias forge='php -d error_reporting="E_ALL & ~E_DEPRECATED" $(which forge)'
These commands are safe to run without confirmation:
# Application logs (Laravel logs)
forge site:logs <site>
# Deployment logs
forge deploy:logs <site>
# PHP-FPM logs
forge php:logs
# Nginx logs
forge nginx:logs
forge php:status
forge nginx:status
forge database:status
forge server:list
forge site:list
forge site:info <site>
The forge command subcommand has a known bug ("Event unresolvable"). Use direct SSH instead:
forge server:list
# Note the IP address for your server
Note: If zero-downtime deployments are enabled, replace
/home/forge/<site>with/home/forge/<site>/currentin all commands below.
# Basic structure
ssh forge@<server-ip> "cd /home/forge/<site> && <command>"
# Examples:
# Check PHP version
ssh forge@<ip> "php -v"
# Run artisan commands (read-only)
ssh forge@<ip> "cd /home/forge/<site> && php artisan --version"
ssh forge@<ip> "cd /home/forge/<site> && php artisan route:list"
ssh forge@<ip> "cd /home/forge/<site> && php artisan config:show app"
# Check queue status
ssh forge@<ip> "cd /home/forge/<site> && php artisan queue:monitor"
# View recent logs
ssh forge@<ip> "tail -100 /home/forge/<site>/storage/logs/laravel.log"
# IMPORTANT: Use echo to see output, escape backslashes
ssh forge@<ip> "cd /home/forge/<site> && php artisan tinker --execute='echo App\\Models\\User::count();'"
# Query examples
ssh forge@<ip> "cd /home/forge/<site> && php artisan tinker --execute='echo App\\Models\\User::where(\"email\", \"like\", \"%@example.com\")->count();'"
# Get recent records
ssh forge@<ip> "cd /home/forge/<site> && php artisan tinker --execute='print_r(App\\Models\\User::latest()->first()->toArray());'"
These commands are blocked by the safety hook and require explicit user confirmation:
forge deploy <site> # Triggers deployment
forge deploy:reset <site> # Resets deployment script
Always use env:pull and env:push for environment changes - never edit .env directly via SSH with sed or echo (error-prone, no backup).
CRITICAL: Do NOT rename or copy the
.env.forge.<id>file!Forge CLI internally tracks the original
.env.forge.<site-id>filename created byenv:pull. Renaming it (e.g.,mv .env.forge.* .env) or copying it (cp .env.forge.* .env) breaks this tracking and causesenv:pushto fail with:"The environment variables for that site have not been downloaded."Edit the
.env.forge.<site-id>file directly, then push.
CRITICAL: Always use the scratchpad directory for env:pull/env:push operations!
The
forge env:pullcommand creates files in the current working directory. If run from a project root, this can overwrite the local.envfile and cause data loss.ALWAYS
cdto the scratchpad directory first before runningenv:pullorenv:push.
# 1. FIRST: Change to the scratchpad directory (REQUIRED)
cd <scratchpad-directory>
# 2. Pull current .env to scratchpad
# Creates .env.forge.<site-id> in current working directory
forge env:pull <site>
# Output: Environment Variables Written To [.env.forge.3023104]
# 3. Edit the .env.forge.<site-id> file DIRECTLY with your changes
# DO NOT rename or copy it — Forge tracks the original filename internally
# - Add new variables
# - Update existing values
# - Remove obsolete variables
# 4. Push back to production (DESTRUCTIVE)
# Forge will prompt: "Would You Like Update The Site Environment File
# With The Contents Of The File [.env.forge.<id>] (yes/no)"
# Pipe "yes" to confirm non-interactively
echo "yes" | forge env:push <site>
# 5. Clean up scratchpad files
rm -f .env.forge.*
# 6. Clear config cache on the server
ssh forge@<ip> "cd /home/forge/<site> && php artisan config:clear"
Example workflow:
# ALWAYS start by changing to scratchpad
cd <scratchpad-directory>
# Pull example.com .env to scratchpad
forge env:pull example.com
# Output: Environment Variables Written To [.env.forge.3023104]
# Edit the pulled file directly (DO NOT rename)
# Use the Edit tool to add/modify variables in .env.forge.3023104
# Push back (pipe "yes" for non-interactive confirmation)
echo "yes" | forge env:push example.com
# Clean up scratchpad
rm -f .env.forge.*
# Clear config cache on server
ssh forge@<ip> "cd /home/forge/example.com && php artisan config:clear"
Common mistake — DO NOT do this:
# WRONG: Renaming breaks Forge's internal file tracking
mv .env.forge.3023104 .env
forge env:push example.com # FAILS: "environment variables not downloaded"
# WRONG: Copying also breaks it
cp .env.forge.3023104 .env
forge env:push example.com # FAILS: same error
Why use the scratchpad:
.env - critical safety measureWhy this workflow is safer:
# These are BLOCKED - require confirmation:
ssh forge@<ip> "... php artisan migrate"
ssh forge@<ip> "... php artisan db:seed"
ssh forge@<ip> "mysql -e 'DELETE FROM ...'"
ssh forge@<ip> "mysql -e 'UPDATE ...'"
ssh forge@<ip> "mysql -e 'DROP ...'"
ssh forge@<ip> "mysql -e 'TRUNCATE ...'"
The deploy script is NOT stored on the server. It's managed via:
When Forge deploys, it sends the script remotely and executes it. Deployment logs and provisioning scripts are stored in /home/forge/.forge/:
/home/forge/.forge/
├── provision-*.sh # Provisioning scripts (server setup)
├── provision-*.output # Provisioning output logs
├── daemon-*.log # Daemon/worker logs
└── scheduled-*.log # Scheduled task logs
Forge supports two deployment modes:
Site deployed directly to /home/forge/<site>/:
/home/forge/<site>/
├── app/
├── public/
├── storage/
├── .env
└── ...
Use /home/forge/<site> for commands.
When enabled, Forge uses Envoyer-style releases:
/home/forge/<site>/
├── current -> releases/20240115120000 # Symlink to active release
├── releases/
│ ├── 20240115120000/ # Current release
│ ├── 20240114100000/ # Previous release
│ └── ...
├── storage/ # Shared storage
└── .env # Shared environment
Use /home/forge/<site>/current for commands.
How to detect which mode: Check if a current symlink exists:
ssh forge@<ip> "ls -la /home/forge/<site>/current 2>/dev/null || echo 'Standard deployment (no /current symlink)'"
# Via Forge CLI
forge site:logs <site>
# Via SSH (more control)
ssh forge@<ip> "tail -200 /home/forge/<site>/storage/logs/laravel.log | grep -A5 'ERROR\|Exception'"
# Horizon status
ssh forge@<ip> "cd /home/forge/<site> && php artisan horizon:status"
# Failed jobs count
ssh forge@<ip> "cd /home/forge/<site> && php artisan tinker --execute='echo DB::table(\"failed_jobs\")->count();'"
# Recent failed jobs
ssh forge@<ip> "cd /home/forge/<site> && php artisan queue:failed"
# Record counts
ssh forge@<ip> "cd /home/forge/<site> && php artisan tinker --execute='echo App\\Models\\User::count();'"
# Check specific record
ssh forge@<ip> "cd /home/forge/<site> && php artisan tinker --execute='print_r(App\\Models\\User::find(1)?->toArray());'"
# Recent deployment log
forge deploy:logs <site>
# Check site directory
ssh forge@<ip> "ls -la /home/forge/<site>"
# For zero-downtime: check current release symlink
ssh forge@<ip> "readlink /home/forge/<site>/current"
ssh forge@<ip> "df -h" # Disk space
ssh forge@<ip> "free -m" # Memory
ssh forge@<ip> "top -bn1 | head -20" # CPU/processes
forge command is broken - Use direct SSH insteadforge logs doesn't exist - Use forge site:logsforge ssh, forge tinker can't be automatedApp\\Models\\User not App\Models\Userecho in tinker - Output won't show without it/current symlink only for zero-downtime deploymentssed, echo >>, or direct edits on production .env. Use forge env:pull → edit locally → forge env:push instead (safer, creates backup, avoids shell escaping issues)cd to the scratchpad directory before running forge env:pull or forge env:push. Running from a project root will overwrite the local .env file!.env.forge.* files - Forge CLI tracks the original .env.forge.<site-id> filename internally. Renaming it (e.g., mv .env.forge.* .env) breaks tracking and env:push will fail. Edit the .env.forge.<site-id> file directly.