Skip to main content

Deploying Sylius on Nomad

· 4 min read
Stephan Hochdörfer
Head of IT Business Operations

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 --chown=www-data:www-data . /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 --chown=www-data:www-data ./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.