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: ```bash\ kamal setup This command will: 1. SSH into your target server. 2. Install Docker (if not present). 3. Install Traefik as a reverse proxy. 4. Boot configured accessories like Redis. 5. Build, push, and pull your Docker images. 6. Start containers and manage traffic routing. For subsequent updates, run `kamal deploy` for seamless zero-downtime rolling updates. 👉 **[Read the complete deep-dive with the full code repository and bonus security checklist on klytron.com](https://klytron.com/blog/moving-off-paas-kamal-2-laravel)**