Deploying Sylius on Nomad
Last year, I've shared how to deploy Sylius with Docker Compose. I will show you how to deploy a Sylius application on a Nomad cluster this time.
Since the Docker images provided by Sylius are not meant to be used in production, and I had some issues building them, I decided to roll my own Docker images for this project. After a bit of experimentation what is needed, I came up with the following Dockerfile
:
ARG PHP_VERSION=8.2
ARG NGINX_VERSION=1.25
ARG ALPINE_VERSION=3.19
ARG PHP_EXTENSION_INSTALLER_VERSION=latest
FROM mlocati/php-extension-installer:${PHP_EXTENSION_INSTALLER_VERSION} AS php_extension_installer
FROM php:${PHP_VERSION}-fpm-alpine${ALPINE_VERSION} AS sylius_php
RUN apk add --no-cache \
acl \
file \
gettext \
unzip \
; \
COPY --from=php_extension_installer /usr/bin/install-php-extensions /usr/local/bin/
RUN install-php-extensions apcu exif gd intl pdo_mysql opcache zip soap
COPY . /var/www/html
RUN mkdir -p /var/www/html/var/cache && \
mkdir -p /var/www/html/var/log && \
chown -R www-data:www-data /var/www/html/var/
VOLUME /var/www/html/private
VOLUME /var/www/html/var
VOLUME /var/www/html/public/media
FROM nginx:${NGINX_VERSION}-alpine${ALPINE_VERSION} AS sylius_nginx
COPY ./public /var/www/html/public
The Dockerfile does not handle the Composer and npm installs as this will be done in our GitLab CI pipeline. All installed files are copied into the Docker image when it is being built.
To build the Docker images in our GitLab CI pipeline, we use Docker Compose. Our docker-compose.deploy.yml
configuration file looks like this:
services:
php:
image: nexus3.loc/customer/sylius-php:${CI_COMMIT_TAG:-latest}
build:
context: .
target: sylius_php
nginx:
image: nexus3.loc/customer/sylius-nginx:${CI_COMMIT_TAG:-latest}
build:
context: .
target: sylius_nginx
Since the build is running in our GitLab CI pipeline, we can rely on the $CI_COMMIT_TAG
variable to get the Git tag that we use to version the images.
The Docker images are built and pushed to our internal Docker registry by running the following commands:
docker compose -f docker-compose.deploy.yml build --no-cache --parallel
docker push nexus3.loc/customer/sylius-php:"${CI_COMMIT_TAG}"
docker push nexus3.loc/customer/sylius-nginx:"${CI_COMMIT_TAG}"
To deploy both the nginx web server and the phpfm backend, a Nomad job file named customer.nomad
is created, which includes all the Nomad configuration logic. Below, you can find a stripped-down version of the Nomad job file we are using for this project:
variable "mysql-host" {
type = string
default = "192.168.1.200"
}
variable "mysql-port" {
type = number
default = 3306
}
variable "tag" {
type = string
# this variable will be passed via CLI parameters!
}
variable "domain" {
type = string
# this variable will be passed via CLI parameters!
}
job "customer-sylius" {
datacenters = ["dc1"]
type = "service"
group "sylius" {
count = 1
network {
port "phpfpm" {
to = 9000
}
port "nginx" {
to = 80
}
}
task "customer-sylius" {
driver = "docker"
template {
data = <<EOH
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=123456789
TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR
DATABASE_URL="mysql://db_user:db_pass@${var.mysql-host}:${var.mysql-port}/db_name?charset=utf8mb4"
EOH
destination = ".env"
env = true
}
config {
image = "nexus3.loc/customer/sylius-php:${var.tag}"
ports = ["phpfpm"]
command = "/bin/ash"
args = [
"-c",
"php /var/www/html/bin/console doctrine:schema:update --force && php /var/www/html/bin/console cache:warm && php-fpm"
]
volumes = [
"/var/www/html/public/media:/var/www/html/public/media"
]
}
resources {
cpu = 240
memory = 1024
}
service {
name = "customer-phpfpm"
provider = "nomad"
port = "phpfpm"
tags = [
"traefikv2.enable=false",
]
}
}
task "customer-nginx" {
driver = "docker"
template {
data = <<EOH
server {
root /var/www/html/public;
listen *:80;
server_name ${var.domain};
location / {
# try to serve file directly, fallback to index.php
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
resolver 127.0.0.11 valid=10s ipv6=off;
set $backendfpm "{{ range nomadService "customer-phpfpm" }}{{ .Address }}:{{ .Port }}{{ end }}";
fastcgi_pass $backendfpm;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
# pretend to Symfony that we are running on HTTPS
fastcgi_param SERVER_PORT "443";
fastcgi_param HTTPS "on";
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}
location ~ \.php$ {
return 404;
}
client_max_body_size 6m;
}
EOH
destination = "local/default.conf"
env = false
}
config {
image = "nexus3.loc/customer/sylius-nginx:${var.tag}"
ports = ["nginx"]
volumes = [
"/var/www/html/public/media:/var/www/html/public/media"
]
}
resources {
cpu = 240
memory = 256
}
service {
name = "customer-nginx"
provider = "nomad"
port = "nginx"
tags = [
"traefik.enable=true",
"traefik.http.routers.customer-sylius.rule=Host(`${var.domain}`)",
"traefik.http.routers.customer-sylius.entrypoints=websecure",
]
}
}
}
}
The Nomad job defines a job group tickets
, which consists of two tasks customer-sylius
and customer-nginx
. Since both tasks are part of the same group, they will run on the same Nomad client instance.
To deploy the application, call the following command. The variables $CI_COMMIT_TAG
and $DOMAIN
are defined in our GitLab CI pipeline:
nomad job run -var="tag=${CI_COMMIT_TAG}" -var="domain=${DOMAIN}" customer.nomad
Running the command above will take a while since Nomad needs to download and start the docker images. After a bit, both Nomad tasks are up and serving traffic via our Traefik instance, hence the traefik
tags used in the customer-nginx
service definition.