Deploy PHP & MySQL with Kamal
As you are already aware, not all websites are static, and as a follow-up on my Deploy with Kamal, I would like to show you how to deploy a PHP website connected to a MySQL database.
This post
In this post, I'll assume that you have some basic knowledge of deploying with Kamal, or have already read Deploy with Kamal. There are steps that I will not do in this post and simply referring to the steps that need to be done without explaining them.
Disclaimer:
I'm on Linux, so the command I'll be using may differ from your operating system.
Prerequisites
I will list a bare minimum of requirements:
- At least one PHP file
- Ruby installed on your local workstation
- A Docker registry, you can use GitHub Container Registry or Docker Hub for public registries.
- A server where it can be deployed to, suggesting an empty server for experimentation.
How to deploy a PHP & MySQL website with Kamal
Preparing the project we want to deploy
First, we will create our project folder, create an index.php and initialise Git.
Open a terminal and navigate to the directory where you want to create the project.
cd ~/Projects/Demos
mkdir kamal-php-website
cd kamal-php-website
Create a PHP file index.php inside the directory.
<?php
$host = getenv('DB_HOST') ?: 'php-kamal-db';
$db = getenv('DB_DATABASE') ?: 'poc_db';
$user = getenv('DB_USER') ?: 'poc_user';
$pass = getenv('DB_PASSWORD') ?: 'user_secret_password';
try {
$pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8mb4", $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
echo "Connected successfully to MySQL!";
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
Now we will need to install Kamal and configure it for our project.
kamal init
We will now adjust the config/deploy.yml file to match the PHP & MySQL application that we are about to deploy. Before we do that, I want to tell you about accessories in Kamal, which is a way to have MySQL, Redis the running on the server as well, all deployed with Kamal next to the container running the application itself.
# Name of your application. Used to uniquely configure containers.
service: com-kamal-php
# Name of the container image.
image: vendor/php-kamal-website
# Deploy to these servers.
servers:
web:
- 192.168.0.100 # Server IP
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
proxy:
ssl: true
host: domain.tld
app_port: 8080 # nginx-php containers run on different default port
# Credentials for your image host.
registry:
# Specify the registry server if you're not using Docker Hub
# server: registry.domain.tld
username: docker-hub-username
# Always use an access token rather than real password (pulled from .kamal/secrets).
password:
- KAMAL_REGISTRY_PASSWORD
# Configure builder setup, the platform you are building for. There are other options to set as well.
builder:
arch: amd64
# --- Inject database credentials into your PHP container ---
env:
clear:
DB_HOST: php-kamal-db # Internal Docker network hostname (service_name-accessory_name)
DB_DATABASE: poc_db
DB_USER: poc_user
secret:
- DB_PASSWORD
# --- MySQL Accessory Configuration ---
accessories:
db:
image: mysql:8.0
host: 192.168.10.72 # Runs on the same server IP
# port: "127.0.0.1:3306:3306" # Optional: map locally if you need direct external GUI access
env:
clear:
MYSQL_DATABASE: poc_db
MYSQL_USER: poc_user
MYSQL_ROOT_HOST: "%"
secret:
- MYSQL_ROOT_PASSWORD
- MYSQL_PASSWORD
directories:
- db-data:/var/lib/mysql # Persist database data on host server
As you see there are env declared in the Kamal configuration file. These are the environment variables that will be passed to the container when it is run. They are used to configure the MySQL database.
The secrets file looks like this, I have removed comments etc. not needed in this example:
# Option 1: Read secrets from the environment
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
# Database secrets
MYSQL_ROOT_PASSWORD="root_secret_password"
MYSQL_PASSWORD="user_secret_password"
DB_PASSWORD="user_secret_password"
What do we need in our Dockerfile?
FROM serversideup/php:8.5-fpm-nginx
# Set the public directory as the Web Root
ENV NGINX_WEB_ROOT=/var/www/html/public
# Include custom Nginx location configs (like /up)
COPY docker/nginx/up.conf /etc/nginx/server-opts.d/up.conf
# Copy application files into the container
COPY --chown=www-data:www-data . /var/www/html
As the Nginx default configuration is different in the serversideup/php:8.5-fpm-nginx image than the one in the nginx:latest image we will do the /up endpoint a little different. If you look into the file docker/nginx/up.conf you will see this, a small configuration that gets appended to the default configuration already in the image.
location = /up {
access_log off;
add_header Content-Type text/plain;
return 200 'OK';
}
Deployment
Remember to commit all changes before deploying.
Now we are ready to deploy our project with Kamal. Let's jump into the project directory and run the kamal deploy command.
kamal deploy
The deployment took on my system 37 seconds, which this time besides the SSL certificate, also included setting up the MySQL database.
That's it!