- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Laravel Deployment Mistakes That Break Production, and How to Avoid Them
About Post
The pull request was reviewed. The tests passed. The feature worked perfectly on staging. Then it hit production and the new emails never went out, the uploaded images returned 404, and the error log was empty because the app couldn't write to it.
None of that was a code bug. It was the deploy.
Laravel makes building apps pleasant, but getting code onto a server has its own set of traps, and most of them fail quietly. Here are the ones I see most often, each with what goes wrong, why, and the fix.
Mistake 1: Forgetting that queue workers are still running yesterday's code
What happens: you deploy a fix to a job class. The bug keeps happening. Or you add a new job and it fails with strange errors about missing classes or methods.
Why: php artisan queue:work is a long-running process. It loaded your application into memory when it started and it doesn't notice new files on disk. Your web requests run the new code; your workers run the old one.
The fix: make this part of every deploy:
php artisan queue:restart
It tells each worker to exit gracefully after its current job, and your process manager (Supervisor, systemd) starts fresh ones with the new code. Two things to check: the workers must actually be supervised, or they won't come back, and the command needs a working cache driver, because that's where the restart signal is stored. On Horizon, use php artisan horizon:terminate instead.
Mistake 2: Not caching config, or caching it and then calling env()
What happens: either the app is noticeably slower than it should be, or, after you finally run config:cache, some settings suddenly come back as null.
Why: without caching, Laravel reads and merges every config file on every request. With caching, it loads one compiled file and stops reading .env altogether. Any env('SOMETHING') call outside the config/ folder then returns null.
The fix: only call env() inside config files, use config() everywhere else, and run this on every deploy:
php artisan optimize # caches config, events, routes and views
A quick search for env( outside config/ before your first cached deploy saves a confusing afternoon.
Mistake 3: Running migrations blindly
What happens: php artisan migrate --force runs in the pipeline, and either a column the old code still uses disappears mid-deploy, or a big table locks while an index is built and requests start timing out.
Why: during a deploy, old and new code overlap for a while. And on large tables, some schema changes take far longer than they did on your small local database.
The fix:
- Read what will run.
php artisan migrate --pretendprints the SQL without executing it. Do this before deploying anything non-trivial. - Expand, then contract. Add new columns first and deploy code that uses them. Remove old columns in a later deploy, once nothing reads them.
- Back up before destructive changes. A
dropColumnhas no undo button. Neither does adown()method that was never tested. - Treat big-table changes as their own task, scheduled for a quiet time, not hidden inside a feature release.
Rule for migrations: every migration in a deploy must be safe to run while the previous version of the code is still serving requests. If it isn't, split it across two deploys.
Mistake 4: The missing storage:link
What happens: users upload profile pictures successfully, and every one of them shows as a broken image.
Why: files on the public disk are saved to storage/app/public, but the web server serves files from public/. The bridge between them is a symbolic link that php artisan storage:link creates. Fresh server, no link.
The fix: run it once per server. With zero-downtime setups that deploy each release into a new folder, make sure storage/ is shared between releases and the link points to the shared one. Otherwise each release starts with an empty storage folder and yesterday's uploads seem to vanish.
Mistake 5: Permissions, and the root-owned log file
What happens: the site works, then suddenly shows a 500 error, and storage/logs has nothing useful in it.
Why: the web server user (often www-data) must be able to write to storage/ and bootstrap/cache/. A sneaky way to break this: someone runs an Artisan command as root, or a cron job runs as root, and Laravel creates today's log file owned by root. Now the web server can't write to the log, so the error about not being able to write is... not written anywhere useful.
The fix: give ownership to the deploy user and the web server group, and run Artisan as the same user as the app:
sudo chown -R deploy:www-data storage bootstrap/cache
sudo chmod -R ug+rwX storage bootstrap/cache
sudo -u deploy php artisan migrate --force
And please, not chmod -R 777. It makes the error go away by making every file writable by everyone on the machine.
Mistake 6: .env surprises
A few classics, all of them avoidable:
APP_DEBUG=truein production. Error pages then show stack traces, file paths and request details to anyone who triggers an error. Production should always haveAPP_ENV=productionandAPP_DEBUG=false.- New variables never added on the server. Someone adds a key locally and the deploy has no idea. Keep
.env.examplecomplete and review it in pull requests. - Running
php artisan key:generateon a live app. The app key encrypts sessions, cookies and any data stored with Laravel's encrypter. Change it and every user is logged out, and encrypted values can no longer be decrypted. - Forgetting the cache. After editing
.envon a server with cached config, nothing changes until you runphp artisan config:cacheagain.
Mistake 7: composer update on the server
Production should install exactly the versions you tested. That's composer install, which reads composer.lock. composer update resolves new versions on the spot, which means production may run a combination of packages nobody has ever tested. Add --no-dev so test tools stay off the server, and --optimize-autoloader for faster class loading.
Put it in a script
Most of these mistakes come from steps someone forgot. Scripts don't forget. A simplified deploy for a single server:
#!/usr/bin/env bash
set -e # stop at the first failing command
php artisan down --retry=60
git pull origin main
composer install --no-dev --optimize-autoloader --no-interaction
npm ci && npm run build
php artisan migrate --force
php artisan optimize
php artisan queue:restart
php artisan up
Whether this runs from a CI/CD pipeline, Laravel Forge, Envoyer or a plain shell script matters less than the fact that it runs the same way every time. The Laravel deployment docs are a good companion for server configuration.
Before your next deploy
- Workers restarted, and actually supervised?
- Config cached, and no
env()outsideconfig/? - Migrations read with
--pretendand safe for the old code? storage:linkin place and storage shared between releases?- Permissions correct, Artisan run as the app user?
APP_DEBUG=false,.envcomplete, app key untouched?composer install, neverupdate?
Which of these has caught you out? And what's the one step you added to your deploy script after learning it the hard way?

Be first to comment it...