Mastodon Skip to main content
Available for Projects

I'm always excited to take on new projects and collaborate with innovative minds.

DevOps

Moving Off PaaS: Deploying Production Laravel Stacks with Kamal 2

PaaS costs scale faster than your user base. Kamal 2 allows you to deploy containerized Laravel applications to any VPS with zero downtime and no vendor lock-in.

Michael K. Laweh
2026-04-23 14:15:00 9 min read
Moving Off PaaS: Deploying Production Laravel Stacks with Kamal 2

Platform-as-a-Service (PaaS) providers make launching a web application simple. You push code, and they handle the infrastructure. However, as your application grows, the cost scales aggressively. What starts as a $20/month hobby server can scale to hundreds or thousands of dollars for simple database upgrades, extra RAM, or custom background workers.

In the past, moving off a PaaS meant adopting complex container orchestrators like Kubernetes or spending hours writing custom server setup scripts.

Kamal 2 (created by 37signals) offers a middle ground. It is an infrastructure-agnostic deployment tool that allows you to deploy containerized applications to any virtual private server (like Hetzner, DigitalOcean, or bare metal) with zero downtime, using a clean SSH-based workflow.

Here is a practical engineering guide to deploying a production Laravel application using Kamal 2.


1. Prerequisites and Docker Setup

To deploy with Kamal, your application must be containerized. We need a production-ready Dockerfile that packages PHP-FPM, Nginx, and the necessary PHP extensions.

Create a Dockerfile in the root of your Laravel project:

FROM php:8.4-fpm-alpine

# Install system dependencies and PHP extensions
RUN apk add --no-cache \
    nginx \
    supervisor \
    postgresql-dev \
    libxml2-dev \
    oniguruma-dev \
    libpng-dev \
    zip \
    unzip \
    git \
    curl \
    bash

RUN docker-php-ext-install pdo_pgsql mbstring xml gd bcmath opcache

# Copy Nginx and Supervisor configs
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/supervisord.conf /etc/supervisord.conf

# Copy application files
WORKDIR /var/www/html
COPY . .

# Install composer dependencies
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN composer install --no-dev --optimize-autoloader --no-interaction

# Set directory permissions
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

# Expose port 80 and boot supervisor
EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

2. Installation and Initializing Kamal

Ensure you have Docker running locally. Then, install Kamal on your local development machine:

gem install kamal

Once installed, run the initialization command in your Laravel project root:

kamal init

This command generates three key files:

  • config/deploy.yml: The primary deployment configuration file.
  • .env: Locally contains secret keys and registry passwords (ignored by git).
  • .kamal/secrets: Scripts for managing secrets securely.

3. Configuring config/deploy.yml

The deploy.yml file defines your servers, your container image registry, and your service configurations. Here is a production configuration setting up a Laravel app and a Redis server on a single VPS:

service: my-laravel-app

image: klytron/my-laravel-app

servers:
  web:
    hosts:
      - 192.0.2.1 # Your VPS IP address
    labels:
      traefik.http.routers.my-laravel-app.rule: "Host(`app.example.com`)"

registry:
  username: klytron
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    DB_CONNECTION: pgsql
    DB_HOST: 192.0.2.1
    DB_PORT: 5432
    DB_DATABASE: my_database
    REDIS_HOST: my-laravel-app-redis
  secret:
    - APP_KEY
    - DB_PASSWORD

accessories:
  redis:
    image: redis:7.2-alpine
    roles:
      - web
    port: "6379:6379"

4. Managing Environment Secrets

To prevent sensitive API keys or database passwords from leaking, Kamal retrieves secrets from your local .env file on deployment and injects them into the running container securely.

Define your local variables in your local .env:

KAMAL_REGISTRY_PASSWORD=dckr_pat_your_docker_hub_token
APP_KEY=base64:your_production_laravel_key
DB_PASSWORD=your_production_database_password

When you deploy, Kamal reads these values and writes them to an encrypted environment file inside the target server.


5. Executing the First Deployment

With your servers, registry, and environment variables configured, run the setup routine:

kamal setup

This command will:

  1. SSH into your target server.
  2. Install Docker (if it is not already present).
  3. Install Traefik (the default reverse proxy used by Kamal to route traffic).
  4. Boot configured accessories (like Redis).
  5. Build your Docker image locally, push it to your registry, and pull it to your servers.
  6. Start your web containers and hand off traffic via Traefik.

For all subsequent code updates, simply run:

kamal deploy

Kamal will perform a zero-downtime, rolling update by booting the new container version, verifying its health check, routing Traefik requests to it, and cleanly stopping the old container.


Conclusion

Kamal 2 offers a deployment experience that rivals standard PaaS providers while allowing you to maintain full control over your server cost and layout. By running standard Docker containers under a light SSH orchestrator, you avoid vendor lock-in and can run production environments on cost-effective VPS servers.

If you are looking to move off an expensive PaaS, set up highly reproducible Dockerized environments, or optimize your DevOps pipeline, let’s schedule a call.

Contact me to plan your infrastructure migration.

Michael K. Laweh
Michael K. Laweh
Author

Senior IT Consultant & Digital Solutions Architect with 16+ years of engineering experience. Founder of LAWEITECH, builder of ScrybaSMS, Nexus Retail OS, and 4 open-source packages on Packagist. Currently building the next generation of AI-integrated enterprise tools.

Have a project in mind?

From AI-integrated platforms to enterprise infrastructure, I architect solutions that deliver measurable business results. Let's talk.

Post Details
Read Time 9 min read
Published 2026-04-23 14:15:00
Category DevOps
Author Michael K. Laweh
Share Article

Related Articles

View All Posts
Jun 04, 2026 • 10 min read
Debugging Postiz & Temporal: A Production Runbook for Self-Hosted Social Media Orchestration

Self-hosting your social media scheduler sounds easy until Temporal wo...

May 07, 2026 • 9 min read
Distributed Background Processing: Scaling Temporal Workflows with Laravel

Standard queues fail when background tasks last days or require comple...

Mar 16, 2026 • 8 min read
How I Finally Conquered Deployment Hell: The PHP Deployment Kit

Stop rewriting the same deployment tasks. Discover how I engineered a...