Laravel Forge CLI
Overview
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.
Setup
# Install globally
composer global require laravel/forge-cli
# Authenticate (opens browser)
forge login
# List available servers/sites
forge server:list
forge site:list
PHP 8.4 Deprecation Warnings
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)'
Safe Read-Only Commands
These commands are safe to run without confirmation:
View Logs
# 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
Status Checks
forge php:status
forge nginx:status
forge database:status
forge server:list
forge site:list
Site Information
forge site:info <site>
Direct SSH (Recommended for Complex Tasks)
The forge command subcommand has a known bug ("Event unresolvable"). Use direct SSH instead:
Get Server IP
forge server:list
# Note the IP address for your server
Run Remote Commands
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"
Tinker (Read-Only Queries)
# 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());'"
Destructive Operations (Require Confirmation)
These commands are blocked by the safety hook and require explicit user confirmation:
Deployment
forge deploy <site> # Triggers deployment
forge deploy:reset <site> # Resets deployment script
Environment Changes
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:
- Prevents overwriting local project
.env- critical safety measure - Creates an isolated workspace for production env changes
- Scratchpad is session-specific and auto-cleaned
- Keeps production credentials out of project directories
Why this workflow is safer:
- Creates a local backup you can review before pushing
- Avoids shell escaping issues with special characters
- Prevents accidental concatenation or corruption
- Easy to diff changes before pushing
Database Operations via SSH
# 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 ...'"
Deployment Script Location
The deploy script is NOT stored on the server. It's managed via:
- Forge web dashboard: Sites → [site] → Deployments → Deploy Script
- Forge API
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
Deployment Modes
Forge supports two deployment modes:
Standard Deployment (Default)
Site deployed directly to /home/forge/<site>/:
/home/forge/<site>/
├── app/
├── public/
├── storage/
├── .env
└── ...
Use /home/forge/<site> for commands.
Zero-Downtime Deployment (Optional)
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)'"
Common Debugging Workflows
1. Check Recent Errors
# 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'"
2. Check Queue Health
# 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"
3. Check Database State
# 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());'"
4. Check Deployment Status
# 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"
5. Check Server Resources
ssh forge@<ip> "df -h" # Disk space
ssh forge@<ip> "free -m" # Memory
ssh forge@<ip> "top -bn1 | head -20" # CPU/processes
Known Issues & Gotchas
forge commandis broken - Use direct SSH insteadforge logsdoesn't exist - Useforge site:logs- Interactive commands don't work -
forge ssh,forge tinkercan't be automated - Escape backslashes in tinker - Use
App\\Models\\UsernotApp\Models\User - Use
echoin tinker - Output won't show without it - PHP 8.4 warnings - Use the alias with error suppression
- Wrong path - Check deployment mode first; use
/currentsymlink only for zero-downtime deployments - Never edit .env via SSH - Don't use
sed,echo >>, or direct edits on production.env. Useforge env:pull→ edit locally →forge env:pushinstead (safer, creates backup, avoids shell escaping issues) - CRITICAL: env:pull/env:push in wrong directory - ALWAYS
cdto the scratchpad directory before runningforge env:pullorforge env:push. Running from a project root will overwrite the local.envfile! - CRITICAL: Never rename
.env.forge.*files - Forge CLI tracks the original.env.forge.<site-id>filename internally. Renaming it (e.g.,mv .env.forge.* .env) breaks tracking andenv:pushwill fail. Edit the.env.forge.<site-id>file directly.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.