Deploying Laravel Inertia to Coolify using Docker Compose (with and without SSR)
In this comprehensive guide, we'll walk through the process of deploying a Laravel application with Inertia to Coolify using Docker Compose. We'll cover both Server-Side Rendering (SSR) and non-SSR setups, allowing you to choose the best option for your project.
Prerequisites
Before we begin, make sure you have the following:
- A Laravel application with Inertia set up
- A Coolify server set up
Step 1: Prepare Your Docker Configuration
First, we need to create a Dockerfile
and a docker-compose.yaml
file in the root of your Laravel project.
Dockerfile
Create a Dockerfile
with the following content:
FROM php:8.4-apache
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
zip \
unzip \
gzip \
mariadb-client \
libpng-dev \
libonig-dev \
libxml2-dev \
libwebp-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
libzip-dev \
libicu-dev
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath zip
RUN docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype
RUN docker-php-ext-install -j$(nproc) gd
RUN docker-php-ext-configure intl
RUN docker-php-ext-install intl
# Enable Apache modules
RUN a2enmod rewrite deflate expires headers
# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www
# Copy existing application directory contents
COPY . /var/www
# Copy Apache virtual host file
COPY docker/apache/000-default.conf /etc/apache2/sites-available/000-default.conf
# Copy custom PHP configuration
COPY docker/php/custom.ini $PHP_INI_DIR/conf.d/
# Install Composer dependencies
RUN composer install --no-interaction --no-dev --prefer-dist
# Install Node.js and npm
RUN curl -sL https://deb.nodesource.com/setup_22.x | bash -
RUN apt-get install -y nodejs
# Install npm dependencies and build assets
RUN npm ci && npm run build
# If you're using SSR, use this instead
# RUN npm ci && npm run build:ssr
# Copy storage directory to a .dist directory
RUN cp -a /var/www/storage /var/www/storage.dist
# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Set as entrypoint
ENTRYPOINT ["docker-entrypoint.sh"]
# Change the document root to public
ENV APACHE_DOCUMENT_ROOT /var/www/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
EXPOSE 80 443
CMD ["apache2-foreground"]
docker-compose.yaml
Create a docker-compose.yaml
file with the following content:
services:
app:
build:
context: .
dockerfile: Dockerfile
image: laravel
container_name: laravel-app
restart: unless-stopped
working_dir: /var/www
volumes:
- storage:/var/www/storage
depends_on:
- db
healthcheck:
test:
- CMD
- curl
- "-f"
- "http://127.0.0.1/up"
interval: 2s
timeout: 10s
retries: 10
labels:
- "traefik.http.middlewares.ipwhitelist.ipwhitelist.sourcerange=0.0.0.0/0,::/0"
- "traefik.http.middlewares.add-forwarded-headers.headers.customrequestheaders.X-Forwarded-For=X-Real-IP"
- "traefik.http.middlewares.upload-limit.buffering.maxRequestBodyBytes=67108864"
# Uncomment the following block if you're using SSR
# ssr:
# image: laravel
# container_name: laravel-ssr
# restart: unless-stopped
# command: ["php", "/var/www/artisan", "inertia:start-ssr"]
# volumes:
# - storage:/var/www/storage
# depends_on:
# - app
# healthcheck:
# test: ["CMD", "curl", "-f", "http://127.0.0.1:13714/health"]
# interval: 2s
# timeout: 10s
# retries: 10
scheduler:
image: laravel
container_name: laravel-scheduler
restart: unless-stopped
command: ["php", "/var/www/artisan", "schedule:work"]
volumes:
- storage:/var/www/storage
depends_on:
- app
db:
image: mariadb:11.4
container_name: laravel-db
restart: unless-stopped
environment:
MARIADB_ROOT_HOST: "%"
MARIADB_DATABASE: ${DB_DATABASE}
MARIADB_ROOT_PASSWORD: ${DB_PASSWORD}
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_USER: ${DB_USERNAME}
volumes:
- dbdata:/var/lib/mysql
healthcheck:
test:
- CMD
- mariadb-admin
- ping
- "-h127.0.0.1"
- "-uroot"
- "-p${DB_PASSWORD}"
interval: 5s
timeout: 20s
retries: 10
If you're not using SSR, keep the ssr
service commented out. If you are using SSR, uncomment the ssr
service block.
Step 2: Create Additional Configuration Files
docker-entrypoint.sh
Create a docker-entrypoint.sh
file in the root of your project:
#!/bin/bash
set -e
# Copy storage directory contents if storage volume is empty
if [ ! "$(ls -A /var/www/storage)" ]; then
cp -a /var/www/storage.dist/. /var/www/storage/
fi
# Set proper permissions
chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
chmod -R 775 /var/www/storage /var/www/bootstrap/cache
# Optimize Laravel application
php /var/www/artisan optimize
# Run original docker-php-entrypoint
exec docker-php-entrypoint "$@"
.dockerignore
Create a .dockerignore
file in the root of your project:
node_modules
vendor
.git
.idea
Apache Configuration
Create a file at docker/apache/000-default.conf
:
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/public
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
Timeout 300
LimitRequestBody 67108864
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/public>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/pdf "access plus 1 month"
ExpiresByType text/x-javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/x-shockwave-flash "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresDefault "access plus 2 days"
</IfModule>
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "no-referrer-when-downgrade"
</IfModule>
</VirtualHost>
PHP Configuration
Create a file at docker/php/custom.ini
:
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
max_input_time = 300
memory_limit = 2048M
Step 3: Prepare Your Laravel Application
- If you're using SSR, modify your
config/inertia.php
to match the Docker configuration:
<?php
return [
'ssr' => [
'enabled' => true,
// Modify it to this
'url' => env('INERTIA_SSR_URL', 'http://ssr:13714'),
// 'bundle' => base_path('bootstrap/ssr/ssr.mjs'),
],
'testing' => [
'ensure_pages_exist' => true,
'page_paths' => [
resource_path('js/pages'),
],
'page_extensions' => [
'js',
'jsx',
'svelte',
'ts',
'tsx',
'vue',
],
],
];
- Modify your
app/Providers/AppServiceProvider.php
to force HTTPS in production:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
// Add this
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}
}
This change ensures that all URLs generated by your application in production will use HTTPS.
- Modify your
bootstrap/app.php
to trust proxies:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// Add this
$middleware->trustProxies(
at: '*',
headers: Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB
);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
This change ensures that all URLs generated by your application in production will use HTTPS.
Step 4: Deploy to Coolify
- Log in to your Coolify dashboard.
- Connect your Git repository to Coolify.
- Create a new project and select "Docker Compose" as the deployment method.
- In the Coolify project settings, make sure to set the following environment variables:
APP_ENV
: Set this toproduction
DB_HOST
: Set this todb
DB_DATABASE
: Your database nameDB_USERNAME
: Your database usernameDB_PASSWORD
: Your database password- Any other environment-specific variables your application needs
.env
- Configure the deployment settings:
- Set the container name
- Set the domain name
- Set the following post-deployment command:
This command will create a symbolic link fromphp artisan storage:link
public/storage
tostorage/app/public
.
- Save your configuration and deploy your application.
Step 5: Post-Deployment Tasks
After successful deployment, go to the Command tab. Execute the following command:
php artisan migrate --force
This command will run your database migrations, ensuring your database schema is up to date.
Conclusion
You have now successfully deployed your Laravel Inertia application to Coolify using Docker Compose, with options for both SSR and non-SSR setups. This configuration provides a scalable and maintainable infrastructure for your application.
Remember to monitor your application's performance and logs, and make adjustments as necessary to optimize its operation in the production environment. If you're using SSR, keep an eye on the SSR service to ensure it's running smoothly and providing the expected performance benefits.
Happy deploying!