Back to blog
DevOps

Deploy Laravel in production: Docker, CapRover and GitOps

29 juillet 2025
8 min de lecture
Laravel Docker CapRover

Deploying a Laravel application in production can seem complex, especially when you want to avoid the php artisan serve command which is absolutely not suitable for a production environment. In this article, I'll present you with a modern and secure solution using Docker and CapRover for automated deployment with GitOps.

Ready-to-use Template

I've created a complete template with Laravel + Inertia.js + Docker + CapRover configuration. Clone and deploy in minutes!

View Template

💡 This article applies particularly to Laravel applications with Inertia.js, but the principles remain valid for any Laravel application.

Why avoid php artisan serve in production?

The php artisan serve command is a lightweight development server, perfect for testing your application locally, but totally unsuitable for production. It doesn't handle scaling, doesn't offer necessary optimizations and presents important security flaws. In production, you need a real web server like Nginx or Apache with PHP-FPM.

Docker: Simple containerization

Docker packages your Laravel application with all its dependencies (PHP, web server, extensions) in a single container. This ensures your app runs identically everywhere: on your machine, on staging, and in production. No more 'it works on my machine' problems!

CapRover: Your own Heroku

CapRover transforms any server into a Platform-as-a-Service like Heroku. With a beautiful web interface, you can deploy applications, manage domains, get automatic SSL certificates, and monitor everything in real-time. It's like having your own Heroku, but cheaper and with full control!

Production-ready Dockerfile

My template includes a multi-stage Dockerfile optimized for Laravel + Inertia.js with Node.js build, PHP-FPM, Caddy web server, and all necessary extensions.

Dockerfile

View on GitHub
ARG PHP_VERSION=${PHP_VERSION:-8.3}

# Node.js build stage
FROM node:20-alpine AS node-build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM php:${PHP_VERSION}-fpm-alpine AS php-system-setup

# Install system dependencies
RUN apk add --no-cache dcron busybox-suid libcap curl zip unzip git

# Install PHP extensions
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
RUN install-php-extensions intl bcmath gd pdo_mysql pdo_pgsql opcache redis uuid exif pcntl zip curl json mbstring xml tokenizer ctype fileinfo

# Install supervisord implementation
COPY --from=ochinchina/supervisord:latest /usr/local/bin/supervisord /usr/local/bin/supervisord

# Install caddy
COPY --from=caddy:2.2.1 /usr/bin/caddy /usr/local/bin/caddy
RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy

# Install composer
COPY --from=composer/composer:2 /usr/bin/composer /usr/local/bin/composer

FROM php-system-setup AS app-setup
# ... Complete configuration in the GitHub repository

Multi-stage build with Node.js, PHP 8.3, Caddy server, and production optimizations

Additional services with CapRover

CapRover makes it incredibly easy to add complementary services to your Laravel application. Everything is deployed with one-click from the app store.

Databases

  • MySQL - Traditional relational database
  • PostgreSQL - Advanced relational database
  • Redis - Cache and sessions

Management Tools

  • phpMyAdmin - MySQL management interface
  • Fider - User feedback system

Nginx Configuration: Critical for Inertia.js

One of the most important aspects when deploying Laravel with Inertia.js is properly configuring Nginx. The default body size limit is far too low for modern applications, especially those using Inertia.js which can send large requests.

⚠️ Critical Configuration

  • client_max_body_size 50M - Essential for handling large Inertia.js requests (can be lowered to 10M)
  • proxy_buffer_size 128k - Optimizes header processing
  • proxy_buffers 4 256k - Improves response handling

Complete Nginx Configuration

<% if (s.forceSsl) { %> server { listen 80; server_name <%-s.publicDomain%>; # Used by Lets Encrypt location /.well-known/acme-challenge/ { root <%-s.staticWebRoot%>; } # Used by CapRover for health check location /.well-known/captain-identifier { root <%-s.staticWebRoot%>; } location / { return 302 https://$http_host$request_uri; } } <% } %> server { <% if (!s.forceSsl) { %> listen 80; <% } if (s.hasSsl) { %> listen 443 ssl http2; ssl_certificate <%-s.crtPath%>; ssl_certificate_key <%-s.keyPath%>; <% } %> client_max_body_size 100M; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; server_name <%-s.publicDomain%>; # 127.0.0.11 is DNS set up by Docker, see: # https://docs.docker.com/engine/userguide/networking/configure-dns/ # https://github.com/moby/moby/issues/20026 resolver 127.0.0.11 valid=10s; # IMPORTANT!! If you are here from an old thread to set a custom port, you do not need to modify this port manually here!! # Simply change the Container HTTP Port from the dashboard HTTP panel set $upstream http://<%-s.localDomain%>:<%-s.containerHttpPort%>; location / { <% if (s.httpBasicAuthPath) { %> auth_basic "Restricted Access"; auth_basic_user_file <%-s.httpBasicAuthPath%>; <% } %> proxy_pass $upstream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; <% if (s.websocketSupport) { %> proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; <% } %> } # Used by Lets Encrypt location /.well-known/acme-challenge/ { root <%-s.staticWebRoot%>; } # Used by CapRover for health check location /.well-known/captain-identifier { root <%-s.staticWebRoot%>; } error_page 502 /captain_502_custom_error_page.html; location = /captain_502_custom_error_page.html { root <%-s.customErrorPagesDirectory%>; internal; } }

Sample configuration - CapRover handles most of this automatically

Automatic deployment with GitHub

CapRover can automatically deploy your application every time you push to your Git repository. Just provide your repository URL and an SSH key, and CapRover handles the rest!

Simple setup:

  1. 1Connect your GitHub repository to CapRover
  2. 2Add an SSH deploy key to your repository
  3. 3Link your CapRover app to your GitHub repository using webhooks
  4. 4Every git push triggers automatic deployment

CapRover configuration

This is the configuration of the CapRover app for GitOps. It is used to deploy the application automatically when a new commit is pushed to the repository.

CapRover configuration for GitOps

Conclusion

This method revolutionizes Laravel application deployment in production. With Docker and CapRover, you get a secure, scalable and fully automated environment. No more manual deployments and 'it works on my machine' problems! GitOps allows you to focus on development while your infrastructure manages itself automatically. One of the biggest advantages of this approach is the simplicity of replication: just clone your repository and redeploy in a few clicks, without any manual configuration. Perfect for creating staging environments, testing, or deploying on multiple servers!

Ready to start your project?

Now that you have all the keys in hand, contact me to discuss your project.

Request a quote