Deploying a Laravel App
What is Laravel?
Laravel is a modern, elegant PHP framework for building web applications with a focus on developer experience and productivity. Created by Taylor Otwell, Laravel provides a comprehensive ecosystem for building anything from simple web applications to complex, scalable enterprise systems.
Key features include:
- Elegant, expressive syntax and conventions
- Eloquent ORM for database interactions
- Comprehensive migration system for database version control
- Built-in authentication and authorization systems
- Powerful routing system with middleware support
- Blade templating engine for views
- Queue system for background jobs
- Cache drivers (Redis, Memcached, File)
- Session and cookie management
- File storage abstraction (Local, S3, etc.)
- RESTful API development with Laravel Sanctum
- Real-time features with WebSockets (Laravel Echo)
- Database seeding and factories
- Testing frameworks (Pest, PHPUnit)
- Artisan CLI for development tasks
- Task scheduling
- Mail sending capabilities
- Comprehensive logging
- Error handling and debugging tools
- Composer dependency management
- Extensive documentation and community
Laravel is ideal for building web applications, RESTful APIs, single-page applications (with Inertia.js or Livewire), content management systems, e-commerce platforms, and real-time applications.
Prerequisites
Before deploying a Laravel application to Klutch.sh, ensure you have:
- PHP 8.1 or later installed on your local machine
- Composer for dependency management
- Git and a GitHub account
- A Klutch.sh account with dashboard access
- PostgreSQL or MySQL for data persistence
- Node.js for frontend assets (npm/yarn)
- Basic understanding of PHP and web application development
Getting Started with Laravel
Step 1: Create a Laravel Project
Using Composer, create a new Laravel project:
composer create-project laravel/laravel laravel-appcd laravel-appThis creates a project structure:
laravel-app/├── app/│ ├── Http/│ │ ├── Controllers/│ │ └── Middleware/│ ├── Models/│ ├── Policies/│ └── Exceptions/├── bootstrap/├── config/├── database/│ ├── migrations/│ ├── seeders/│ └── factories/├── public/│ ├── index.php│ └── css/├── resources/│ ├── views/│ └── js/├── routes/│ ├── api.php│ ├── web.php│ └── console.php├── storage/├── tests/├── .env├── .env.example├── composer.json├── artisan└── package.jsonStep 2: Configure Environment
Create a .env file from the example:
cp .env.example .envphp artisan key:generateEdit .env:
APP_NAME=LaravelAppAPP_ENV=localAPP_KEY=base64:encrypted_key_hereAPP_DEBUG=trueAPP_URL=http://localhost
# Database configurationDB_CONNECTION=pgsqlDB_HOST=127.0.0.1DB_PORT=5432DB_DATABASE=laravel_dbDB_USERNAME=postgresDB_PASSWORD=password
# Cache configurationCACHE_DRIVER=redisREDIS_HOST=127.0.0.1REDIS_PASSWORD=nullREDIS_PORT=6379
# Session configurationSESSION_DRIVER=cookieSESSION_LIFETIME=120
# Queue configurationQUEUE_CONNECTION=sync
# Mail configurationMAIL_MAILER=smtpMAIL_HOST=smtp.mailtrap.ioMAIL_PORT=587MAIL_USERNAME=your_usernameMAIL_PASSWORD=your_passwordStep 3: Create Database Models
Create an Item model with migration:
php artisan make:model Item -mEdit app/Models/Item.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Model;
class Item extends Model{ use HasFactory;
protected $fillable = ['name', 'description', 'price'];
protected $casts = [ 'price' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', ];
public function scopeSearch($query, $search) { return $query->where('name', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%"); }}Edit database/migrations/xxxx_xx_xx_create_items_table.php:
<?php
use Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema;
return new class extends Migration{ public function up(): void { Schema::create('items', function (Blueprint $table) { $table->id(); $table->string('name', 100)->unique(); $table->text('description')->nullable(); $table->integer('price'); $table->timestamps(); $table->index('name'); }); }
public function down(): void { Schema::dropIfExists('items'); }};Step 4: Create API Resources
Create an item resource:
php artisan make:resource ItemResourceEdit app/Http/Resources/ItemResource.php:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;use Illuminate\Http\Resources\Json\JsonResource;
class ItemResource extends JsonResource{ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'description' => $this->description, 'price' => $this->price, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; }}Step 5: Create API Controller
Create a controller:
php artisan make:controller Api/ItemController --api --model=ItemEdit app/Http/Controllers/Api/ItemController.php:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;use App\Http\Resources\ItemResource;use App\Models\Item;use Illuminate\Http\Request;use Illuminate\Http\Response;
class ItemController extends Controller{ public function index(Request $request) { $items = Item::paginate($request->get('per_page', 10)); return ItemResource::collection($items); }
public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:100|unique:items', 'description' => 'nullable|string|max:500', 'price' => 'required|integer|min:0', ]);
$item = Item::create($validated); return new ItemResource($item); }
public function show(Item $item) { return new ItemResource($item); }
public function update(Request $request, Item $item) { $validated = $request->validate([ 'name' => 'string|max:100|unique:items,name,' . $item->id, 'description' => 'nullable|string|max:500', 'price' => 'integer|min:0', ]);
$item->update($validated); return new ItemResource($item); }
public function destroy(Item $item) { $item->delete(); return response()->noContent(); }}Step 6: Create Routes
Edit routes/api.php:
<?php
use App\Http\Controllers\Api\ItemController;use Illuminate\Support\Facades\Route;
Route::get('/health', function () { return response()->json([ 'status' => 'healthy', 'service' => 'laravel-app' ]);});
Route::apiResource('items', ItemController);
Route::get('/items/stats', function () { return response()->json([ 'total_items' => \App\Models\Item::count(), 'timestamp' => now(), ]);});Step 7: Install Frontend Dependencies
npm installnpm run buildUpdate package.json for production builds:
{ "private": true, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "devDependencies": { "@tailwindcss/forms": "^0.5.3", "alpinejs": "^3.x.x", "autoprefixer": "^10.4.12", "postcss": "^8.4.24", "tailwindcss": "^3.3.0", "vite": "^4.0.0" }}Step 8: Build and Test Locally
# Install dependenciescomposer installnpm install
# Generate application keyphp artisan key:generate
# Create database and run migrationsphp artisan migrate
# Build frontend assetsnpm run build
# Start development serverphp artisan serve
# Test the APIcurl http://localhost:8000/api/healthDeploying Without a Dockerfile
Klutch.sh uses Nixpacks to automatically detect and build your Laravel application from your source code.
Prepare Your Repository
- Initialize a Git repository and commit your code:
git initgit add .git commit -m "Initial Laravel app commit"- Create a
.gitignorefile:
/node_modules/public/hot/public/storage/storage/*.key/vendor.env.env.backup.env.production.phpunit.result.cacheHomestead.jsonHomestead.yamlauth.jsonnpm-debug.logyarn-error.log.DS_Store- Create
.env.productionfor production configuration:
APP_NAME=LaravelAppAPP_ENV=productionAPP_DEBUG=falseAPP_URL=https://example-app.klutch.sh
DB_CONNECTION=pgsqlDB_HOST={DB_HOST}DB_PORT=5432DB_DATABASE={DB_NAME}DB_USERNAME={DB_USER}DB_PASSWORD={DB_PASSWORD}
CACHE_DRIVER=redisREDIS_HOST={REDIS_HOST}REDIS_PORT=6379
SESSION_DRIVER=cookieQUEUE_CONNECTION=sync-
Ensure project includes:
composer.jsonin rootpackage.jsonfor frontend assetspublic/index.phpas entry point- Database migrations
-
Push to GitHub:
git remote add origin https://github.com/YOUR_USERNAME/laravel-app.gitgit branch -M maingit push -u origin mainDeploy to Klutch.sh
-
Log in to Klutch.sh dashboard.
-
Click “Create a new project” and provide a project name.
-
Inside your project, click “Create a new app”.
-
Repository Configuration:
- Select your GitHub repository containing the Laravel app
- Select the branch to deploy (typically
main)
-
Traffic Settings:
- Select “HTTP” as the traffic type
-
Port Configuration:
- Set the internal port to 8080 (recommended for PHP applications on Klutch.sh)
-
Environment Variables: Set the following environment variables in the Klutch.sh dashboard:
APP_ENV: Set toproductionfor live deploymentsAPP_DEBUG: Set tofalsefor productionDB_CONNECTION: Set to your database type (pgsql or mysql)DB_HOST: Your database hostnameDB_PORT: Your database portDB_DATABASE: Your database nameDB_USERNAME: Your database usernameDB_PASSWORD: Your database passwordCACHE_DRIVER: Set toredisfor better performanceREDIS_HOST: Your Redis hostname (if using caching)SESSION_DRIVER: Set tocookieorredisAPP_URL: Your deployed application URL
-
Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:
BUILD_COMMAND: Default runscomposer install && npm install && npm run build && php artisan migrateSTART_COMMAND: Default isphp artisan serve --host 0.0.0.0 --port 8080
-
Region, Compute, and Instances:
- Choose your desired region for optimal latency
- Select compute resources (Starter for prototypes, Pro/Premium for production)
- Set the number of instances (start with 1-2, scale as needed based on traffic)
-
Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.
-
Once deployment completes, your app will be accessible at
example-app.klutch.sh.
Verifying the Deployment
Test your deployed app:
curl https://example-app.klutch.sh/api/healthYou should receive:
{ "status": "healthy", "service": "laravel-app"}Access the API:
https://example-app.klutch.sh/api/itemsDeploying With a Dockerfile
If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.
Create a Multi-Stage Dockerfile
Create a Dockerfile in your project root:
# Build stageFROM composer:2.6 AS builder
WORKDIR /app
# Copy composer filesCOPY composer.json composer.lock ./
# Install PHP dependenciesRUN composer install --no-dev --optimize-autoloader
# Node stage for frontendFROM node:18-alpine AS node-builder
WORKDIR /app
# Copy package filesCOPY package.json package-lock.json ./
# Install dependencies and buildRUN npm install && npm run build
# Runtime stageFROM php:8.2-fpm-alpine
WORKDIR /app
# Install required PHP extensions and toolsRUN apk add --no-cache \ postgresql-dev \ mysql-dev \ nginx \ curl \ supervisor
RUN docker-php-ext-install \ pdo \ pdo_mysql \ pdo_pgsql \ mysqli \ bcmath \ ctype \ fileinfo \ json \ mbstring \ tokenizer \ xml
# Copy application filesCOPY --from=builder /app/vendor ./vendorCOPY --from=node-builder /app/node_modules ./node_modulesCOPY --from=node-builder /app/public ./publicCOPY --chown=www-data:www-data . .
# Create necessary directoriesRUN mkdir -p storage/logs storage/app storage/framework && \ chown -R www-data:www-data storage bootstrap/cache public
# Configure NginxRUN echo "server { \ listen 8080; \ server_name _; \ root /app/public; \ index index.php; \ client_max_body_size 100M; \ location / { \ try_files \\\$uri \\\$uri/ /index.php?\\\$query_string; \ } \ location ~ \.php\$ { \ fastcgi_pass 127.0.0.1:9000; \ fastcgi_index index.php; \ include fastcgi_params; \ fastcgi_param SCRIPT_FILENAME \\\$document_root\\\$fastcgi_script_name; \ } \}" > /etc/nginx/conf.d/default.conf
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:8080/api/health || exit 1
# Expose portEXPOSE 8080
# Create non-root userRUN addgroup -g 1000 laravel && \ adduser -D -u 1000 -G laravel laravel
USER laravel
# Start servicesCMD ["sh", "-c", "php-fpm & nginx -g 'daemon off;'"]Deploy the Dockerfile Version
- Push your code with the Dockerfile to GitHub:
git add Dockerfilegit commit -m "Add Dockerfile for custom build"git push-
Log in to Klutch.sh dashboard.
-
- Select your GitHub repository and branch
- Set traffic type to “HTTP”
- Set the internal port to 8080
- Add environment variables
- Click “Create”
-
Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.
Database Configuration
PostgreSQL Setup
PostgreSQL is recommended for Laravel applications. Configure in .env:
DB_CONNECTION=pgsqlDB_HOST={DB_HOSTNAME}DB_PORT=5432DB_DATABASE={DB_DATABASE}DB_USERNAME={DB_USERNAME}DB_PASSWORD={DB_PASSWORD}MySQL Setup
Configure MySQL in .env:
DB_CONNECTION=mysqlDB_HOST={DB_HOSTNAME}DB_PORT=3306DB_DATABASE={DB_DATABASE}DB_USERNAME={DB_USERNAME}DB_PASSWORD={DB_PASSWORD}Running Migrations
Migrations run during deployment or manually with:
php artisan migratephp artisan db:seedDatabase Seeding
Create a seeder:
php artisan make:seeder ItemSeederEdit database/seeders/ItemSeeder.php:
<?php
namespace Database\Seeders;
use App\Models\Item;use Illuminate\Database\Seeder;
class ItemSeeder extends Seeder{ public function run(): void { Item::create([ 'name' => 'Sample Item', 'description' => 'This is a sample item', 'price' => 9999, ]); }}Run seeders:
php artisan db:seedAuthentication and Authorization
Setup Authentication
Install Laravel Sanctum for API authentication:
php artisan install:apiCreate an authentication controller:
php artisan make:controller Api/AuthControllerEdit app/Http/Controllers/Api/AuthController.php:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;use App\Models\User;use Illuminate\Http\Request;use Illuminate\Support\Facades\Hash;
class AuthController extends Controller{ public function register(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|min:8|confirmed', ]);
$user = User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'password' => Hash::make($validated['password']), ]);
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([ 'user' => $user, 'token' => $token, ], 201); }
public function login(Request $request) { $validated = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]);
$user = User::where('email', $validated['email'])->first();
if (!$user || !Hash::check($validated['password'], $user->password)) { return response()->json(['message' => 'Invalid credentials'], 401); }
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([ 'user' => $user, 'token' => $token, ]); }
public function logout(Request $request) { $request->user()->currentAccessToken()->delete(); return response()->json(['message' => 'Logged out successfully']); }}Protect Routes
Edit routes/api.php:
<?php
use App\Http\Controllers\Api\ItemController;use App\Http\Controllers\Api\AuthController;
Route::post('/register', [AuthController::class, 'register']);Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () { Route::apiResource('items', ItemController::class); Route::post('/logout', [AuthController::class, 'logout']);});Caching Configuration
Redis Setup
Configure Redis in .env:
CACHE_DRIVER=redisREDIS_HOST=127.0.0.1REDIS_PASSWORD=nullREDIS_PORT=6379Using Cache
use Illuminate\Support\Facades\Cache;
// Save to cacheCache::put('items', $items, now()->addHours(1));
// Retrieve from cache$items = Cache::get('items');
// Cache foreverCache::forever('static_config', $config);
// Forget cacheCache::forget('items');Queue Configuration
Setup Queues
Edit .env:
QUEUE_CONNECTION=redisCreate a job:
php artisan make:job ProcessItemEdit app/Jobs/ProcessItem.php:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;use Illuminate\Queue\SerializesModels;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Contracts\Queue\ShouldQueue;
class ProcessItem implements ShouldQueue{ use Queueable, SerializesModels, InteractsWithQueue;
public function __construct(public $item) { }
public function handle(): void { // Process item logic here }}Dispatch a job:
use App\Jobs\ProcessItem;
ProcessItem::dispatch($item);File Storage
Configure Storage
Edit config/filesystems.php:
'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), ],],Using Storage
use Illuminate\Support\Facades\Storage;
// Store file$path = Storage::disk('local')->put('uploads', $request->file('file'));
// Retrieve file$file = Storage::disk('local')->get('uploads/filename.ext');
// Delete fileStorage::disk('local')->delete('uploads/filename.ext');Logging and Monitoring
Configure Logging
Edit .env:
LOG_CHANNEL=stackLOG_LEVEL=infoUsing Logging
use Illuminate\Support\Facades\Log;
Log::info('Item created', ['id' => $item->id]);Log::error('Database error', ['error' => $exception->getMessage()]);Log::debug('Debug information', $debugData);Persistent Storage for Uploads and Logs
Adding Persistent Volume
- In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
- Click “Add Volume”
- Set the mount path:
/app/storage(for uploads, logs, and cache) - Set the size based on your needs (e.g., 20 GB for uploads and logs)
- Save and redeploy
Configure Storage Path
The storage directory should be mounted as persistent storage:
# Ensure storage permissionschmod -R 755 storagechmod -R 777 storage/logs storage/appCustom Domains
To serve your Laravel application from a custom domain:
- In the Klutch.sh app dashboard, navigate to “Custom Domains”
- Click “Add Custom Domain”
- Enter your domain (e.g.,
api.example.com) - Follow the DNS configuration instructions provided
Example DNS configuration:
api.example.com CNAME example-app.klutch.shUpdate APP_URL environment variable:
APP_URL=https://api.example.comTroubleshooting
Issue 1: Database Migration Fails
Problem: Migrations fail during deployment with “table already exists” or connection errors.
Solution:
- Verify database connection credentials in environment variables
- Check if migrations have already been run on the database
- Ensure database user has CREATE/ALTER TABLE permissions
- Test connection string format with database client
- Check database server is running and accessible
- Review migration file syntax for errors
Issue 2: Storage Permission Denied
Problem: File uploads fail with permission denied errors.
Solution:
- Mount persistent storage for storage directory
- Set proper directory permissions:
chmod 777 storage/app - Ensure web server user (www-data) has write permissions
- Check disk space availability on persistent volume
- Verify file upload size limits in php.ini
Issue 3: Session Lost Between Requests
Problem: Session data is not persisting across requests.
Solution:
- Configure persistent storage for session files
- Use Redis for session driver in production
- Verify SESSION_DRIVER in .env is set correctly
- Check browser cookie settings
- Ensure session path is writable and persistent
Issue 4: Slow Application Performance
Problem: Application responds slowly to requests.
Solution:
- Enable caching with Redis for frequently accessed data
- Optimize database queries and add indexes
- Use eager loading in Eloquent queries
- Enable query caching with Redis
- Monitor performance metrics in dashboard
- Consider scaling to additional instances
Issue 5: Memory or Disk Space Issues
Problem: Application crashes due to memory or disk space limits.
Solution:
- Increase instance compute resources
- Increase persistent storage size
- Optimize database queries and indexes
- Implement log rotation
- Clean up old uploaded files
- Monitor resource usage via dashboard
Best Practices for Production Deployment
-
Environment Variables: Use
.envfor configurationAPP_ENV=productionAPP_DEBUG=falseDB_PASSWORD={STRONG_PASSWORD} -
Database Optimization: Add indexes and optimize queries
$items = Item::with('relations')->paginate(15); -
Caching Strategy: Use Redis for cache, sessions, and queues
CACHE_DRIVER=redisSESSION_DRIVER=redisQUEUE_CONNECTION=redis -
Enable HTTPS: Enforce secure requests
config/app.php 'force_https' => true, -
Input Validation: Always validate user input
$validated = $request->validate(['email' => 'required|email']); -
Error Handling: Don’t expose sensitive information
if (!$user) {return response()->json(['message' => 'Not found'], 404);} -
Security Headers: Configure security headers
// Configured by default in Laravel -
Database Transactions: Use transactions for critical operations
DB::transaction(function () {$item->save();$log->record();}); -
Logging: Log important events and errors
Log::info('User action', ['user_id' => $user->id, 'action' => 'login']); -
Rate Limiting: Implement rate limiting for APIs
Route::middleware('throttle:60,1')->group(function () {Route::apiResource('items', ItemController::class);});
Resources
- Laravel Official Website
- Laravel Documentation
- Eloquent ORM Guide
- Routing Documentation
- Database Migrations
- Authentication Guide
- Laravel Sanctum Documentation
Conclusion
Deploying Laravel applications to Klutch.sh provides a robust, scalable platform for building and running modern web applications. Laravel’s comprehensive ecosystem, elegant syntax, and developer-friendly tools make it ideal for building complex applications.
Key takeaways:
- Use Nixpacks for quick deployments with automatic PHP and Node.js detection
- Use Docker for complete control over PHP extensions and dependencies
- Leverage Eloquent ORM for efficient database operations
- Implement proper authentication with Sanctum for APIs
- Use caching with Redis for improved performance
- Configure persistent storage for file uploads and logs
- Enable HTTPS and implement security best practices
- Implement structured logging for monitoring and debugging
- Use environment variables for configuration management
- Monitor application health and performance metrics
- Scale instances based on traffic and demand
For additional help, refer to the Laravel documentation or Klutch.sh support resources.