Deploying Laravel on Kubernetes
Dockerize Laravel
The first step in our journey to deploying a Laravel application on Kubernetes is to Dockerize our Laravel application. While the process might seem daunting at first, with the right guidance and approach, you can create a robust, production-ready system. In this blog post, we'll break down each step to help you Dockerize your Laravel application effectively. This guide will walk you through configuring your Dockerfile, setting up environments, and leveraging Docker Compose. Let's dive in!
Why Not Use Laravel Sail? π
Laravel Sail is optimized for local development and isn't always the best fit for production environments. It introduces overhead and complexity that you might want to avoid in a production setting. For these reasons, using Docker and Kubernetes directly can lead to a more streamlined and efficient production setup.
Key Considerations When Dockerizing a Laravel Application π
- Persistent Volumes: Ensure you store files in a persistent volume.
- Log Management: Properly configure logging channels to output logs to
stdout
and use Kubernetes for handling them.
Step-by-Step Guide to Dockerizing Your Laravel Application π οΈ
Step 1: Install Prerequisites
- Docker: Install Docker
- Docker Compose: Install Docker Compose
Step 2: Configure Logging to stdout
In config/logging.php
, add a new log channel for stdout
:
return [
'channels' => [
'stdout' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDOUT_FORMATTER'),
'with' => [
'stream' => 'php://stdout',
],
],
],
];
Update your .env
file:
LOG_CHANNEL=stdout
Step 3: Use Redis for Sessions
Install the predis/predis
package:
composer require predis/predis
Or add it directly to composer.json
:
{
"require": {
"predis/predis": "^1.1"
}
}
Update your .env
file for Redis sessions:
SESSION_DRIVER=redis
Step 4: Force HTTPS in Production
In app/Providers/AppServiceProvider.php
, enforce HTTPS for production:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}
}
Step 5: Prepare the .dockerignore
File
Create a .dockerignore
file in the root of your project with the following contents:
/vendor
/node_modules
Step 6: Create the Dockerfile
Create a Dockerfile:
FROM composer:2.2 AS composer_base
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql dom pcntl"
ARG PHP_PECL_EXTS="redis"
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN addgroup -S composer && \
adduser -S composer -G composer && \
chown -R composer /opt/apps/laravel.deployonkubernetes.com && \
apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \
pecl install ${PHP_PECL_EXTS} && \
docker-php-ext-enable ${PHP_PECL_EXTS} && \
apk del build-dependencies
USER composer
COPY --chown=composer composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
COPY --chown=composer . .
RUN composer install --no-dev --prefer-dist
Testing the Composer Stage
Build the Docker image and ensure it installs all dependencies:
docker build . --target composer_base
Step 7: Create the Frontend Stage
For the frontend, add the following to the Dockerfile:
FROM node:20-alpine AS frontend
COPY --from=composer_base /opt/apps/laravel.deployonkubernetes.com /opt/apps/laravel.deployonkubernetes.com
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN npm install && npm run build
Testing the Frontend Stage
Build the frontend image to verify:
docker build . --target frontend
Step 8: Create the CLI Container
Add a new CLI stage to your Dockerfile:
FROM php:8.3-alpine AS cli
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql dom pcntl"
ARG PHP_PECL_EXTS="redis"
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \
pecl install ${PHP_PECL_EXTS} && \
docker-php-ext-enable ${PHP_PECL_EXTS} && \
apk del build-dependencies
COPY --from=composer_base /opt/apps/laravel.deployonkubernetes.com /opt/apps/laravel.deployonkubernetes.com
COPY --from=frontend /opt/apps/laravel.deployonkubernetes.com/public /opt/apps/laravel.deployonkubernetes.com/public
Testing the CLI Container
Build the CLI image:
docker build . --target cli
Step 9: Create the FPM Container
Create the FPM server stage in your Dockerfile:
FROM php:8.3-fpm-alpine AS fpm_server
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql dom pcntl"
ARG PHP_PECL_EXTS="redis"
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \
pecl install ${PHP_PECL_EXTS} && \
docker-php-ext-enable ${PHP_PECL_EXTS} && \
apk del build-dependencies
USER www-data
COPY --from=composer_base --chown=www-data /opt/apps/laravel.deployonkubernetes.com /opt/apps/laravel.deployonkubernetes.com
COPY --from=frontend --chown=www-data /opt/apps/laravel.deployonkubernetes.com/public /opt/apps/laravel.deployonkubernetes.com/public
RUN php artisan event:cache && php artisan route:cache && php artisan view:cache
Testing the FPM Container
Build the FPM server image:
docker build . --target fpm_server
Step 10: Create the Web Server Container
Create a directory and an Nginx configuration template:
mkdir -p docker && touch docker/nginx.conf
# docker/nginx.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
root /opt/apps/laravel.deployonkubernetes.com/public;
index index.php index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_pass ${FPM_HOST};
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
}
location ~ /\.ht { deny all; }
location ~ /\.(?!well-known).* { deny all; }
}
Add the web server stage to your Dockerfile:
FROM nginx:1.20-alpine AS web_server
WORKDIR /opt/apps/laravel.deployonkubernetes.com
COPY docker/nginx.conf /etc/nginx/templates/default.conf.template
COPY --from=frontend /opt/apps/laravel.deployonkubernetes.com/public /opt/apps/laravel.deployonkubernetes.com/public
Testing the Web Server Container
Build the web server image:
docker build . --target web_server
Step 11: Create the Cron Container
Add a cron stage to the Dockerfile:
FROM cli AS cron
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN touch laravel.cron && echo "* * * * * cd /opt/apps/laravel.deployonkubernetes.com && php artisan schedule:run" >> laravel.cron && crontab laravel.cron
CMD ["crond", "-l", "2", "-f"]
Testing the Cron Container
Build the cron image:
docker build . --target cron
Step 12: Define the Default Build
Specify the default stage at the end of your Dockerfile:
FROM cli
Final Dockerfile
Here is the final Dockerfile:
FROM composer:2.2 AS composer_base
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql dom pcntl"
ARG PHP_PECL_EXTS="redis"
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN addgroup -S composer && \
adduser -S composer -G composer && \
chown -R composer /opt/apps/laravel.deployonkubernetes.com && \
apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \
pecl install ${PHP_PECL_EXTS} && \
docker-php-ext-enable ${PHP_PECL_EXTS} && \
apk del build-dependencies
USER composer
COPY --chown=composer composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
COPY --chown=composer . .
RUN composer install --no-dev --prefer-dist
FROM node:20-alpine AS frontend
COPY --from=composer_base /opt/apps/laravel.deployonkubernetes.com /opt/apps/laravel.deployonkubernetes.com
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN npm install && npm run build
FROM php:8.3-alpine AS cli
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql dom pcntl"
ARG PHP_PECL_EXTS="redis"
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \
pecl install ${PHP_PECL_EXTS} && \
docker-php-ext-enable ${PHP_PECL_EXTS} && \
apk del build-dependencies
COPY --from=composer_base /opt/apps/laravel.deployonkubernetes.com /opt/apps/laravel.deployonkubernetes.com
COPY --from=frontend /opt/apps/laravel.deployonkubernetes.com/public /opt/apps/laravel.deployonkubernetes.com/public
FROM php:8.3-fpm-alpine AS fpm_server
ARG PHP_EXTS="bcmath ctype fileinfo mbstring pdo pdo_mysql dom pcntl"
ARG PHP_PECL_EXTS="redis"
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN apk add --virtual build-dependencies --no-cache ${PHPIZE_DEPS} openssl ca-certificates libxml2-dev oniguruma-dev && \
docker-php-ext-install -j$(nproc) ${PHP_EXTS} && \
pecl install ${PHP_PECL_EXTS} && \
docker-php-ext-enable ${PHP_PECL_EXTS} && \
apk del build-dependencies
USER www-data
COPY --from=composer_base --chown=www-data /opt/apps/laravel.deployonkubernetes.com /opt/apps/laravel.deployonkubernetes.com
COPY --from=frontend --chown=www-data /opt/apps/laravel.deployonkubernetes.com/public /opt/apps/laravel.deployonkubernetes.com/public
RUN php artisan event:cache && php artisan route:cache && php artisan view:cache
FROM nginx:1.20-alpine AS web_server
WORKDIR /opt/apps/laravel.deployonkubernetes.com
COPY docker/nginx.conf /etc/nginx/templates/default.conf.template
COPY --from=frontend /opt/apps/laravel.deployonkubernetes.com/public /opt/apps/laravel.deployonkubernetes.com/public
FROM cli AS cron
WORKDIR /opt/apps/laravel.deployonkubernetes.com
RUN touch laravel.cron && echo "* * * * * cd /opt/apps/laravel.deployonkubernetes.com && php artisan schedule:run" >> laravel.cron && crontab laravel.cron
CMD ["crond", "-l", "2", "-f"]
FROM cli
Step 13: Set Up Docker Compose
Create docker-compose.yml
:
version: '3'
services:
laravel.fpm:
build:
context: .
target: fpm_server
image: laravel.deployonkubernetes.com/fpm_server
environment:
APP_DEBUG: "true"
volumes:
- '.:/opt/apps/laravel.deployonkubernetes.com'
networks:
- laravel.deployonkubernetes.com
laravel.web:
build:
context: .
target: web_server
image: laravel.deployonkubernetes.com/web_server
ports:
- '8080:80'
environment:
FPM_HOST: "laravel.fpm:9000"
volumes:
- './public:/opt/apps/laravel.deployonkubernetes.com/public'
networks:
- laravel.deployonkubernetes.com
laravel.cron:
build:
context: .
target: cron
image: laravel.deployonkubernetes.com/cron
volumes:
- '.:/opt/apps/laravel.deployonkubernetes.com'
networks:
- laravel.deployonkubernetes.com
laravel.frontend:
build:
context: .
target: frontend
command: ["npm", "run", "watch"]
image: laravel.deployonkubernetes.com/frontend
volumes:
- '.:/opt/apps/laravel.deployonkubernetes.com'
- '/opt/app/node_modules/'
networks:
- laravel.deployonkubernetes.com
networks:
laravel.deployonkubernetes.com:
Testing with Docker Compose
Start your containers:
docker-compose up -d
You can access your application at localhost:8080
.
Step 14: Add MySQL to Docker Compose
Add the MySQL service to docker-compose.yml
:
services:
mysql:
image: 'mysql:8.0'
ports:
- '3306:3306'
environment:
MYSQL_ROOT_PASSWORD: 'secret'
MYSQL_DATABASE: 'laravel'
MYSQL_USER: 'user'
MYSQL_PASSWORD: 'secret'
volumes:
- 'laravel.deployonkubernetes.com-mysql:/var/lib/mysql'
networks:
- laravel.deployonkubernetes.com
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-psecret"]
retries: 3
timeout: 5s
volumes:
laravel.deployonkubernetes.com-mysql:
Step 15: Run Migrations
To test the MySQL service, run migrations:
docker-compose exec laravel.fpm php artisan migrate
Dockerizing a Laravel application involves various steps, but this guide aims to simplify the process. With a properly set up Docker environment, your application will be more scalable and easier to manage. Start with these steps and adjust configurations to fit your needs. Happy coding! π
Pushing Images to a Container Registry
What is a Container Registry?
A container registry is a service that stores and distributes container images. It's a centralized repository where container images, which are essential for containerized applications, are stored and managed. Popular container registries include Docker Hub, GitLab, Google Container Registry, and Amazon Elastic Container Registry.
Why Use a Container Registry?
Container registries provide several benefits:
- Centralized Storage: Keeps all your images in one place.
- Version Control: You can tag images and maintain different versions.
- Scalability: Easily scalable between different environments.
- Security: Provides access control and other security features.
Step-by-Step Guide to Push Docker Images to GitLab
Let's dive into the practical aspect of this tutorial. We will use GitLab as our container registry for this example.
Step 1: Login to GitLab
First, you need to log in to your GitLab container registry. Open your terminal and execute the following command:
$ docker login registry.gitlab.com
You will be prompted to enter your GitLab username and password. Once you've successfully logged in, you should see a message confirming your login.
Step 2: Build Your Docker Images
Now that we're logged in, it's time to build our Docker images. Here's how you do it:
$ docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cli-v0.0.1 --target cli
$ docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:fpm_server-v0.0.1 --target fpm_server
$ docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:web_server-v0.0.1 --target web_server
$ docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cron-v0.0.1 --target cron
In this step:
docker build .
: Builds the Docker image from the current directory.-t registry.gitlab.com/[your_username]...
: Tags the image with the specified name.--target [stage_name]
: Specifies the target stage to build.
Step 3: Push Your Docker Images
Now that our Docker images are built, let's push them to the GitLab container registry:
$ docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cli-v0.0.1
$ docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:fpm_server-v0.0.1
$ docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:web_server-v0.0.1
$ docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cron-v0.0.1
Each command pushes a different Docker image to the registry.
Step 4: Simplify the Process with a Makefile
To streamline the build and release process, we can utilize a Makefile. This enables us to run all the commands needed to build and push our images with a single command.
First, create a Makefile
in your project directory with the following content:
.PHONY: docker-build docker-release
VERSION := $(shell jq -r .version composer.json)
docker-build:
docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cli-$(VERSION) --target cli
docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:fpm_server-$(VERSION) --target fpm_server
docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:web_server-$(VERSION) --target web_server
docker build . -t registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cron-$(VERSION) --target cron
docker-release:
docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cli-$(VERSION)
docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:fpm_server-$(VERSION)
docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:web_server-$(VERSION)
docker push registry.gitlab.com/[your_username]/laravel.deployonkubernetes.com:cron-$(VERSION)
With this Makefile, you can build and release your Docker images with a single command:
make docker-build
make docker-release
If you need to update the version of your images, simply change the version in the composer.json
file and rerun the make commands.
Feel free to leave your comments below or share this post if you found it helpful! π