Deploying Laravel Applications with Kamal: A Real-World Example
What is Kamal?
Kamal is an deployment tool created by the team behind Ruby on Rails that simplifies Docker-based deployments. It’s designed to make deployment as simple as possible while still providing the flexibility and power of Docker. While it was originally created for Rails applications, it works excellently with Laravel and other PHP applications.
Why Choose Kamal?
Before diving into the implementation, let’s discuss why you might want to consider Kamal for your Laravel deployments:
- Simple Configuration: Kamal uses a single YAML file for most configuration
- Zero-downtime Deployments: Built-in support for rolling deployments
- Docker-based: Leverages Docker’s containerization benefits
- Built-in Health Checks: Automatic monitoring of your application’s health
- SSL Support: Easy SSL configuration with Let’s Encrypt
- Multi-server Support: Can deploy to multiple servers with ease
Real-World Example: Deploying Ticketi
Let me walk you through how I deployed Ticketi, a Laravel-based ticketing system, using Kamal. This will serve as a practical example of Kamal in action.
Prerequisites
Before we begin, make sure you have:
- Docker installed on both your local and production machines
- SSH access to your production server
- Git installed
- Ruby installed (for Kamal)
- A GitHub account (we’ll be using GitHub Container Registry)
Step 1: Setting Up the Dockerfile
First, let’s look at our Dockerfile. We’re using a multi-stage build to optimize our image size and build process:
FROM serversideup/php:8.3-fpm-nginx-alpine AS base
ENV AUTORUN_ENABLED=1
ENV PHP_OPCACHE_ENABLE=1
ENV SSL_MODE="off"
# PHP extensions and dependencies
RUN apk add --update busybox-suid python3 make g++ sqlite sqlite-dev && \
install-php-extensions pdo_sqlite bcmath gd exif
This base image from ServerSideUp comes with PHP-FPM and Nginx pre-configured, saving us significant setup time. We’re using Alpine Linux for a smaller image size, and we’re only installing the PHP extensions we actually need.
Step 2: Configuring Kamal
The heart of our deployment configuration lives in config/deploy.yml. Here’s our production setup:
service: kamal
image: ghcr.io/devalade/ticketi
servers:
web:
- ticketi.devalade.me
cron:
hosts:
- ticketi.devalade.me
cmd: php artisan schedule:work
options:
health-cmd: healthcheck-schedule
proxy:
ssl: true
host: ticketi.devalade.me
app_port: 8080
Let’s break down what’s happening here:
- Service Configuration: We’re defining our service name and Docker image location
- Server Setup: We’re configuring both web and cron servers
- SSL: We’re enabling SSL for secure connections
- Health Checks: We’ve added a health check for our scheduled tasks
Step 3: Environment and Storage Configuration
One of the key aspects of any deployment is managing environment variables and persistent storage. Here’s how we’ve configured ours:
env:
clear:
APP_NAME: "Ticketi"
APP_ENV: "production"
APP_DEBUG: false
APP_URL: "https://ticketi.devalade.me"
DB_CONNECTION: "sqlite"
DB_DATABASE: "/var/www/html/data/database.sqlite"
STORAGE_MEDIA_DISK_PATH: "/app-media/"
For persistent storage, we’ve configured two volumes:
volumes:
- "data:/var/www/html/data"
- "/storage:/app-media/"
This ensures our database and media files persist between deployments.
Step 4: Setting Up Redis for Caching
We’ve also included Redis for improved performance:
accessories:
redis:
image: valkey/valkey:8
host: ticketi.devalade.me
directories:
- redis-data:/data
Step 5: Deployment Process
With everything configured, deployment is surprisingly simple:
-
Initial Setup:
kamal setup -
Deploy:
kamal deploy
Kamal handles everything else - pulling the image, setting up containers, configuring the proxy, and ensuring zero-downtime deployment.
Useful Commands for Management
Here are some commands I frequently use for managing the deployment:
# View logs
kamal logs
# Access the shell
kamal shell
# Access Laravel Tinker
kamal tinker
# Restart services
kamal restart
Common Challenges and Solutions
During our deployment, we encountered and solved several common issues:
- Database Permissions: Ensure your data directory has the correct permissions (775) and is owned by www-data
- Asset Building: Make sure your assets are built during the Docker build process
- Redis Connection: If Redis isn’t connecting, verify the accessory service is running
Security Best Practices
We’ve implemented several security measures:
- Environment Variables: Sensitive data is stored in secret environment variables
- SSL: Enabled by default for all traffic
- File Permissions: Proper user and group settings for all directories
- Container Security: Running as non-root user (www-data)
Conclusion
Kamal has proven to be an excellent choice for deploying our Laravel application. It provides a perfect balance between simplicity and flexibility, making it suitable for both small and large applications. While it might have a slight learning curve if you’re new to Docker, the benefits of having a robust, scalable deployment solution make it worth the investment.
The complete setup we’ve discussed here is running in production for Ticketi, handling our needs efficiently. Whether you’re deploying your first Laravel application or looking to improve your existing deployment process, Kamal is definitely worth considering.
Resources for Further Learning
Have you tried Kamal for your Laravel deployments? I’d love to hear about your experiences in the comments below!