Deploy Laravel in production: Docker, CapRover and GitOps
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 GitHubARG 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:
- 1Connect your GitHub repository to CapRover
- 2Add an SSH deploy key to your repository
- 3Link your CapRover app to your GitHub repository using webhooks
- 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.

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