Skip to content

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:

Terminal window
composer create-project laravel/laravel laravel-app
cd laravel-app

This 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.json

Step 2: Configure Environment

Create a .env file from the example:

Terminal window
cp .env.example .env
php artisan key:generate

Edit .env:

APP_NAME=LaravelApp
APP_ENV=local
APP_KEY=base64:encrypted_key_here
APP_DEBUG=true
APP_URL=http://localhost
# Database configuration
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=laravel_db
DB_USERNAME=postgres
DB_PASSWORD=password
# Cache configuration
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Session configuration
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
# Queue configuration
QUEUE_CONNECTION=sync
# Mail configuration
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=587
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password

Step 3: Create Database Models

Create an Item model with migration:

Terminal window
php artisan make:model Item -m

Edit 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:

Terminal window
php artisan make:resource ItemResource

Edit 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:

Terminal window
php artisan make:controller Api/ItemController --api --model=Item

Edit 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

Terminal window
npm install
npm run build

Update 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

Terminal window
# Install dependencies
composer install
npm install
# Generate application key
php artisan key:generate
# Create database and run migrations
php artisan migrate
# Build frontend assets
npm run build
# Start development server
php artisan serve
# Test the API
curl http://localhost:8000/api/health

Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your Laravel application from your source code.

Prepare Your Repository

  1. Initialize a Git repository and commit your code:
Terminal window
git init
git add .
git commit -m "Initial Laravel app commit"
  1. Create a .gitignore file:
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
.DS_Store
  1. Create .env.production for production configuration:
APP_NAME=LaravelApp
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example-app.klutch.sh
DB_CONNECTION=pgsql
DB_HOST={DB_HOST}
DB_PORT=5432
DB_DATABASE={DB_NAME}
DB_USERNAME={DB_USER}
DB_PASSWORD={DB_PASSWORD}
CACHE_DRIVER=redis
REDIS_HOST={REDIS_HOST}
REDIS_PORT=6379
SESSION_DRIVER=cookie
QUEUE_CONNECTION=sync
  1. Ensure project includes:

    • composer.json in root
    • package.json for frontend assets
    • public/index.php as entry point
    • Database migrations
  2. Push to GitHub:

Terminal window
git remote add origin https://github.com/YOUR_USERNAME/laravel-app.git
git branch -M main
git push -u origin main

Deploy to Klutch.sh

  1. Log in to Klutch.sh dashboard.

  2. Click “Create a new project” and provide a project name.

  3. Inside your project, click “Create a new app”.

  4. Repository Configuration:

    • Select your GitHub repository containing the Laravel app
    • Select the branch to deploy (typically main)
  5. Traffic Settings:

    • Select “HTTP” as the traffic type
  6. Port Configuration:

    • Set the internal port to 8080 (recommended for PHP applications on Klutch.sh)
  7. Environment Variables: Set the following environment variables in the Klutch.sh dashboard:

    • APP_ENV: Set to production for live deployments
    • APP_DEBUG: Set to false for production
    • DB_CONNECTION: Set to your database type (pgsql or mysql)
    • DB_HOST: Your database hostname
    • DB_PORT: Your database port
    • DB_DATABASE: Your database name
    • DB_USERNAME: Your database username
    • DB_PASSWORD: Your database password
    • CACHE_DRIVER: Set to redis for better performance
    • REDIS_HOST: Your Redis hostname (if using caching)
    • SESSION_DRIVER: Set to cookie or redis
    • APP_URL: Your deployed application URL
  8. Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:

    • BUILD_COMMAND: Default runs composer install && npm install && npm run build && php artisan migrate
    • START_COMMAND: Default is php artisan serve --host 0.0.0.0 --port 8080
  9. 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)
  10. Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.

  11. Once deployment completes, your app will be accessible at example-app.klutch.sh.

Verifying the Deployment

Test your deployed app:

Terminal window
curl https://example-app.klutch.sh/api/health

You should receive:

{
"status": "healthy",
"service": "laravel-app"
}

Access the API:

https://example-app.klutch.sh/api/items

Deploying 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 stage
FROM composer:2.6 AS builder
WORKDIR /app
# Copy composer files
COPY composer.json composer.lock ./
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader
# Node stage for frontend
FROM node:18-alpine AS node-builder
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install dependencies and build
RUN npm install && npm run build
# Runtime stage
FROM php:8.2-fpm-alpine
WORKDIR /app
# Install required PHP extensions and tools
RUN 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 files
COPY --from=builder /app/vendor ./vendor
COPY --from=node-builder /app/node_modules ./node_modules
COPY --from=node-builder /app/public ./public
COPY --chown=www-data:www-data . .
# Create necessary directories
RUN mkdir -p storage/logs storage/app storage/framework && \
chown -R www-data:www-data storage bootstrap/cache public
# Configure Nginx
RUN 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 check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/api/health || exit 1
# Expose port
EXPOSE 8080
# Create non-root user
RUN addgroup -g 1000 laravel && \
adduser -D -u 1000 -G laravel laravel
USER laravel
# Start services
CMD ["sh", "-c", "php-fpm & nginx -g 'daemon off;'"]

Deploy the Dockerfile Version

  1. Push your code with the Dockerfile to GitHub:
Terminal window
git add Dockerfile
git commit -m "Add Dockerfile for custom build"
git push
  1. Log in to Klutch.sh dashboard.

  2. Create a new app:

    • Select your GitHub repository and branch
    • Set traffic type to “HTTP”
    • Set the internal port to 8080
    • Add environment variables
    • Click “Create”
  3. 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=pgsql
DB_HOST={DB_HOSTNAME}
DB_PORT=5432
DB_DATABASE={DB_DATABASE}
DB_USERNAME={DB_USERNAME}
DB_PASSWORD={DB_PASSWORD}

MySQL Setup

Configure MySQL in .env:

DB_CONNECTION=mysql
DB_HOST={DB_HOSTNAME}
DB_PORT=3306
DB_DATABASE={DB_DATABASE}
DB_USERNAME={DB_USERNAME}
DB_PASSWORD={DB_PASSWORD}

Running Migrations

Migrations run during deployment or manually with:

Terminal window
php artisan migrate
php artisan db:seed

Database Seeding

Create a seeder:

Terminal window
php artisan make:seeder ItemSeeder

Edit 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:

Terminal window
php artisan db:seed

Authentication and Authorization

Setup Authentication

Install Laravel Sanctum for API authentication:

Terminal window
php artisan install:api

Create an authentication controller:

Terminal window
php artisan make:controller Api/AuthController

Edit 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=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Using Cache

use Illuminate\Support\Facades\Cache;
// Save to cache
Cache::put('items', $items, now()->addHours(1));
// Retrieve from cache
$items = Cache::get('items');
// Cache forever
Cache::forever('static_config', $config);
// Forget cache
Cache::forget('items');

Queue Configuration

Setup Queues

Edit .env:

QUEUE_CONNECTION=redis

Create a job:

Terminal window
php artisan make:job ProcessItem

Edit 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 file
Storage::disk('local')->delete('uploads/filename.ext');

Logging and Monitoring

Configure Logging

Edit .env:

LOG_CHANNEL=stack
LOG_LEVEL=info

Using 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

  1. In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
  2. Click “Add Volume”
  3. Set the mount path: /app/storage (for uploads, logs, and cache)
  4. Set the size based on your needs (e.g., 20 GB for uploads and logs)
  5. Save and redeploy

Configure Storage Path

The storage directory should be mounted as persistent storage:

Terminal window
# Ensure storage permissions
chmod -R 755 storage
chmod -R 777 storage/logs storage/app

Custom Domains

To serve your Laravel application from a custom domain:

  1. In the Klutch.sh app dashboard, navigate to “Custom Domains”
  2. Click “Add Custom Domain”
  3. Enter your domain (e.g., api.example.com)
  4. Follow the DNS configuration instructions provided

Example DNS configuration:

api.example.com CNAME example-app.klutch.sh

Update APP_URL environment variable:

APP_URL=https://api.example.com

Troubleshooting

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

  1. Environment Variables: Use .env for configuration

    APP_ENV=production
    APP_DEBUG=false
    DB_PASSWORD={STRONG_PASSWORD}
  2. Database Optimization: Add indexes and optimize queries

    $items = Item::with('relations')->paginate(15);
  3. Caching Strategy: Use Redis for cache, sessions, and queues

    CACHE_DRIVER=redis
    SESSION_DRIVER=redis
    QUEUE_CONNECTION=redis
  4. Enable HTTPS: Enforce secure requests

    config/app.php
    'force_https' => true,
  5. Input Validation: Always validate user input

    $validated = $request->validate(['email' => 'required|email']);
  6. Error Handling: Don’t expose sensitive information

    if (!$user) {
    return response()->json(['message' => 'Not found'], 404);
    }
  7. Security Headers: Configure security headers

    // Configured by default in Laravel
  8. Database Transactions: Use transactions for critical operations

    DB::transaction(function () {
    $item->save();
    $log->record();
    });
  9. Logging: Log important events and errors

    Log::info('User action', ['user_id' => $user->id, 'action' => 'login']);
  10. Rate Limiting: Implement rate limiting for APIs

    Route::middleware('throttle:60,1')->group(function () {
    Route::apiResource('items', ItemController::class);
    });

Resources


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.