Deploying Laravel 11 Inertia Vue to Coolify using Docker Compose (with and without SSR)

September 15, 2024 (2mo ago)

Deploying Laravel 11 Inertia Vue to Coolify using Docker Compose (with and without SSR)

In this comprehensive guide, we'll walk through the process of deploying a Laravel 11 application with Inertia and Vue.js 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:

  1. A Laravel 11 application with Inertia and Vue.js set up
  2. A Coolify account and 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.2-apache
 
# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip \
    libwebp-dev \
    libjpeg62-turbo-dev \
    libfreetype6-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
RUN docker-php-ext-configure gd --with-webp --with-jpeg --with-freetype
RUN docker-php-ext-install -j$(nproc) gd
 
# Enable Apache mod_rewrite
RUN a2enmod rewrite
 
# 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/
 
# Increase PHP memory limit
RUN echo "memory_limit = 512M" >> $PHP_INI_DIR/conf.d/docker-php-memlimit.ini
 
# Install Composer dependencies
RUN composer install --no-interaction --no-dev --prefer-dist
 
# Install Node.js and npm
ENV NODE_VERSION=20.16.0
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
ENV NVM_DIR=/root/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="/root/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
 
# Install npm dependencies and build assets
RUN npm install && npm run build
 
# Change ownership of storage and bootstrap/cache
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
 
# Ensure storage and bootstrap/cache are writable
RUN chmod -R 775 /var/www/storage /var/www/bootstrap/cache
 
# Copy storage directory to a .dist directory
RUN cp -a /var/www/storage /var/www/storage.dist
 
# Copy bootstrap/cache directory to a .dist directory
RUN cp -a /var/www/bootstrap/cache /var/www/bootstrap/cache.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
      - cache:/var/www/bootstrap/cache
    networks:
      - laravel
    depends_on:
      - db
    healthcheck:
      test:
        - CMD
        - curl
        - "-f"
        - "http://127.0.0.1"
      interval: 2s
      timeout: 10s
      retries: 10
    labels:
      - "traefik.http.middlewares.upload-limit.buffering.maxRequestBodyBytes=67108864"
      - "traefik.http.routers.myapp.middlewares=upload-limit@docker"
 
  scheduler:
    image: laravel
    container_name: laravel-scheduler
    restart: unless-stopped
    command: ["php", "/var/www/artisan", "schedule:work"]
    volumes:
      - storage:/var/www/storage
      - cache:/var/www/bootstrap/cache
    networks:
      - laravel
    depends_on:
      - app
 
  # 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
  #     - cache:/var/www/bootstrap/cache
  #   networks:
  #     - laravel
  #   depends_on:
  #     - app
 
  db:
    image: mariadb:11.4
    container_name: laravel-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
    volumes:
      - dbdata:/var/lib/mysql
    networks:
      - laravel
    healthcheck:
      test:
        - CMD
        - mariadb-admin
        - ping
        - "-h127.0.0.1"
        - "-uroot"
        - "-p${DB_PASSWORD}"
      interval: 5s
      timeout: 20s
      retries: 10
 
networks:
  laravel:
    driver: bridge
 
volumes:
  storage:
    driver: local
  cache:
    driver: local
  dbdata:
    driver: local

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
 
# Copy bootstrap/cache directory contents if cache volume is empty
if [ ! "$(ls -A /var/www/bootstrap/cache)" ]; then
    cp -a /var/www/bootstrap/cache.dist/. /var/www/bootstrap/cache/
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
 
# Clear and cache routes and config
php /var/www/artisan route:clear
php /var/www/artisan route:cache
php /var/www/artisan config:clear
php /var/www/artisan config:cache
 
# 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
    Timeout 300
 
    <Directory /var/www/public>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
 
    # Handle HTTPS when behind a proxy
    SetEnvIf X-Forwarded-Proto "https" HTTPS=on
 
    # Logging
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
 
    # Compression
    <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>
 
    # Caching
    <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>
 
    # Security Headers
    <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>
 
    # Limit request body to 64MB
    LimitRequestBody 67108864
</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 = 512M

Step 3: Prepare Your Laravel Application

  1. If you're using SSR, make sure you have set up Inertia SSR properly in your Laravel application.
  2. 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
    {
        if ($this->app->environment('production')) {
            URL::forceScheme('https');
        }
    }
}

This change ensures that all URLs generated by your application in production will use HTTPS.

Step 4: Deploy to Coolify

  1. Log in to your Coolify dashboard.
  2. Connect your Git repository to Coolify.
  3. Create a new project and select "Docker Compose" as the deployment method.
  4. In the Coolify project settings, make sure to set the following environment variables:
    • DB_HOST: Set this to the name of your database service in the Docker Compose file (e.g., db)
    • DB_DATABASE: Your database name
    • DB_USERNAME: Your database username
    • DB_PASSWORD: Your database password
    • Any other environment-specific variables your application needs .env
  5. Configure the deployment settings:
    • Set the container name
    • Set the domain name
    • Set the following post-deployment command:
      php artisan storage:link
      
      This command will create a symbolic link from public/storage to storage/app/public.
  6. 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 11 Inertia Vue 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.

Key points to remember:

  1. The AppServiceProvider modification ensures HTTPS usage in production.
  2. The php artisan storage:link command is run as a post-deployment task to set up proper storage linking.
  3. Database migrations are run using the Coolify Command tab after deployment.

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!