Automating Laravel Deployments with Deployer

Automating Laravel Deployments with Deployer

Automating Laravel Deployments with Deployer

Manual deployments invite mistakes: forgotten cache clears, missing assets, downtime during uploads. Deployer is a PHP deployment tool that automates releases with a safe, repeatable, zero-downtime flow. In this guide you’ll install Deployer, configure hosts, define tasks (Composer, Vite build, migrations, cache), and wire in a tiny UI to display the current deployed version.

1 — Install Deployer

You can install Deployer globally with Composer or use its PHAR. Composer keeps it versioned alongside your toolchain.

# Install Deployer globally
composer global require deployer/deployerCode language: Bash (bash)

This makes the dep command available on your machine (ensure ~/.composer/vendor/bin or ~/.config/composer/vendor/bin is on your PATH). Alternatively, download the PHAR and run php dep.phar.

2 — Initialize in Your Project

Deployer can scaffold a Laravel-ready config. Run this inside your project root to create deploy.php:

dep init --template=LaravelCode language: Bash (bash)

The generated deploy.php contains common tasks (Composer install, symlink, caches). You’ll customize hosts, shared files, writable directories, and hooks next.

3 — Configure Hosts & Paths

Tell Deployer where to SSH and where releases live on the server. Use a non-root user (e.g., deploy) with key-based auth.

// deploy.php (excerpt)
namespace Deployer;

require 'recipe/laravel.php';

set('application', 'your-app');
set('repository', 'git@github.com:your-org/your-repo.git');
set('keep_releases', 5);

host('your-server.com')
    ->set('remote_user', 'deploy')
    ->set('deploy_path', '/var/www/your-app');Code language: PHP (php)

deploy_path will contain releases/, shared/, and a symlink current. Each deployment creates a timestamped release and atomically switches current to the new one for zero-downtime.

4 — Shared Files & Writable Directories

Persist environment files and user data across releases. Mark storage/cache as writable by the web server user.

// deploy.php (continued)
add('shared_files', ['.env']);
add('shared_dirs',  ['storage']);
add('writable_dirs', ['storage', 'bootstrap/cache']);
set('writable_mode', 'chmod'); // or 'acl' if setfacl is availableCode language: PHP (php)

Deployer creates symlinks from the new release to shared/, so uploads and logs survive code updates. Ensure ownership/permissions (e.g., www-data) are correct on the server.

5 — Composer, Vite Build & Optimizations

Hook a build step after Composer, then warm Laravel caches for faster boot times.

// deploy.php (build & optimize hooks)
task('build', function () {
    run('cd {{release_path}} && npm ci --no-audit --no-fund');
    run('cd {{release_path}} && npm run build');
});

after('deploy:vendors', 'build');

task('artisan:optimize', function () {
    run('cd {{release_path}} && php artisan config:cache && php artisan route:cache && php artisan view:cache');
});

after('artisan:migrate', 'artisan:optimize');Code language: PHP (php)

npm ci ensures reproducible installs; npm run build compiles your assets (Vite). Running Laravel’s cache commands after migrations keeps the app snappy on first request.

6 — Database Migrations & Queues

Migrate safely during deploy, then restart workers so they load new code. If you use Horizon, call its artisan command.

// deploy.php (migrate & queues)
after('deploy:symlink', 'artisan:migrate'); // Deployer's recipe uses --force in prod

task('queue:restart', function () {
    run('cd {{release_path}} && php artisan queue:restart');
});
after('deploy:symlink', 'queue:restart');Code language: PHP (php)

artisan migrate runs with --force in production to apply schema changes. queue:restart signals workers to gracefully restart and pick up the new code without killing in-flight jobs.

7 — Expose Version & Rollbacks

Write the current Git commit to a file during deploy, show it in an admin page, and keep a few releases for quick rollbacks.

// deploy.php (version stamp)
task('app:stamp_version', function () {
    run('cd {{release_path}} && git rev-parse --short HEAD > REVISION || echo $(date +%s) > REVISION');
});
after('deploy:symlink', 'app:stamp_version');Code language: PHP (php)

This stores a short commit hash (or timestamp) in REVISION so you can see exactly what’s live. Deployer also keeps previous releases; revert quickly with dep rollback.

// routes/web.php (admin-only version page)
use Illuminate\\Support\\Facades\\Gate;

Route::middleware(['auth'])->get('/admin/version', function () {
    abort_unless(Gate::allows('viewAdmin'), 403);
    $revision = base_path('REVISION');
    $value = file_exists($revision) ? trim(file_get_contents($revision)) : 'unknown';
    return view('admin.version', ['revision' => $value]);
});Code language: PHP (php)

This route reads the REVISION file (written at deploy time) and displays it for admins. It’s helpful when correlating bug reports to a specific release.

<!-- resources/views/admin/version.blade.php -->
@extends('layouts.app')
@section('content')
<div class="container">
  <h1 class="mb-4">Deployed Version</h1>
  <p><strong>Revision:</strong> {{ $revision }}</p>
  <p class="text-muted">Tip: Keep 5 recent releases to enable fast rollbacks with Deployer.</p>
</div>
@endsectionCode language: HTML, XML (xml)

Keeping version info visible reduces confusion across teams and speeds up incident response. Since releases are kept on disk, you can roll back instantly if a migration or bug slips through.

8 — CI/CD Integration (Optional)

Trigger deployments from your pipeline after tests and builds pass. A common pattern is: test → build assets → create release artifact → dep deploy. For a full pipeline walkthrough, see Step-by-Step CI/CD Pipeline Setup for Laravel 12 on GitHub Actions.

# .github/workflows/deploy.yml (excerpt)
name: Deploy
on:
  workflow_dispatch:
  push:
    branches: [ \"main\" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
      - name: Install Deployer
        run: composer global require deployer/deployer
      - name: Deploy to Production
        env:
          SSH_AUTH_SOCK: /tmp/ssh_agent.sock
        run: ~/.composer/vendor/bin/dep deploy production -nCode language: YAML (yaml)

This minimal workflow installs PHP and Deployer, then executes dep deploy. Use OIDC/SSH keys securely via repository secrets; add caching for speed as needed.

Wrapping Up

Deployer brings reproducible, zero-downtime releases to your Laravel stack: atomic symlinks, shared storage, safe migrations, warmed caches, and quick rollbacks. Add a small version UI for visibility and trigger the process from CI/CD to move fast without breaking prod.

What’s Next

0 Comments

Leave a Comment

Your email address will not be published. Required fields are marked *

Add Comment *

Name *

Email *

Keep Reading...

How to Use Laravel Dusk for Browser Testing
How to Use Laravel Dusk for Browser Testing

End-to-end browser testing ensures that your application works exactly as a user would experience it. Laravel Dusk provides a simple API to…

Using Laravel Factories and Seeders for Test Data
Using Laravel Factories and Seeders for Test Data

Populating reliable test and demo data is essential for development speed, realistic QA, and repeatable CI. In this guide, you’ll learn what…

How to Schedule Jobs in Laravel with Task Scheduling
How to Schedule Jobs in Laravel with Task Scheduling

Laravel’s task scheduling system allows you to automate repetitive jobs such as clearing caches, sending out reports, or syncing data. Instead of…