How To Install Grav CMS Using Docker Swarm

You'll need a server, docker swarm, ssl and nginx installed.

Create a folder and cd into it.

Create a Dockerfile:

FROM php:7.4-fpm

ENV DEBIAN_FRONTEND noninteractive

# Get the basic stuff
RUN apt-get update && \
    apt-get -y upgrade && \
    apt-get install -y \
    sudo net-tools procps nano \
    libgmp-dev \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    iputils-ping \
    libxml2-dev \
    unzip \
    libonig-dev \
    libzip-dev \
    libsodium-dev \
    libonig-dev



RUN  docker-php-ext-install \
     pdo \
     pdo_mysql \
     mysqli \
     mbstring \
     tokenizer \
     xml \
     ctype \
     json \
     zip \
     intl \
     bcmath \
     sodium \
     sockets \
     gd \
     exif

RUN apt-get update && \
    apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libpng-dev

RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
    docker-php-ext-install gd

# Create user with sudo privileges
RUN useradd -ms /bin/bash -u 1000 durbok && \
    usermod -aG sudo durbok
# New added for disable sudo password
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Set as default user
USER durbok
WORKDIR /var/www/html

ENV DEBIAN_FRONTEND teletype

CMD ["php-fpm"]

Build docker image:

docker build -t your_docker_image_name:tag .

Push docker image to your docker repo if you have it. You can use Docker hub or a private repo.

Create docker-compose.yml:

version: "3.7"

services:

  prod:
    image: ur_built_image_here:php:7.4-fpm
    networks:
      - durbok-net
    deploy:
      placement:
        constraints:
        - node.role == manager
      replicas: 1
      restart_policy:
        condition: on-failure
    volumes:
      - ./grav-admin:/var/www/html
networks:
  durbok-net:
    external: true

Create a folder for grav code (grav-admin in my case). Cd in to it, then download zip from https://getgrav.org/downloads and unzip it. You can remove *.zip afterwards.

Create a script (I've called it chmod.sh):

#!/bin/sh
chown -R $USER:www-data .
find . -type f -exec chmod 664 {} \;
find ./bin -type f -exec chmod 775 {} \;
find . -type d -exec chmod 775 {} \;
find . -type d -exec chmod +s {} \;

Deploy grav:

docker stack deploy -c docker-compose.yml --with-registry-auth chose_name

Enter docker container and run chmod.sh.

docker exec -it container_name bash
sudo sh chmod.sh
exit

In case php-fpm didn't start you can run manually inside container:

php-fpm &

Nginx - I am also using docker for nginx which is also in a swarm mode.
Example:

version: '3.7'

services:
  prod:
    image: nginx:stable-alpine
    volumes:
      - ./nginx-conf:/etc/nginx/conf.d
      - ./ssl:/etc/nginx/ssl
      - ./webfolder:/var/www/html/webfolder
    networks:
      - durbok-net
    deploy:
      placement:
        constraints:
          - node.role == manager
      replicas: 1
      restart_policy:
        condition: on-failure
    ports:
      - 80:80
      - 443:443

networks:
  durbok-net:
    external: true

volumes:
  nginx-conf:

Nginx somename.conf:

server {
  listen 80;
  listen [::]:80;
  server_name urdomain.dev;
  rewrite ^ https://$http_host$request_uri? permanent;
}

server {

  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name urdomain.dev;
  root /var/www/html/webfolder/grav-admin;
  try_files $uri $uri/ /index.php;

  index index.html index.php;
  client_max_body_size 256M;


  error_log  /var/log/nginx/grav_error.log;
  access_log /var/log/nginx/grav_access.log;


  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }


  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass grav_prod:9000; # this is docker service name 
    fastcgi_index index.php;
    include fastcgi_params;
    ## this is tricky part ### after scipt_filename I had to put /var/www/html cause that's where my code was in php-fpm docker container
    fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
  }


  location = /favicon.ico {
    log_not_found off;
    access_log off;
  }

  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }

  location ~*  \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 365d;
  }

  location ~*  \.(pdf)$ {
    expires 30d;
  }

  location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 90d;
    add_header Cache-Control "public, no-transform";
  }


location ~ /\.(?!well-known).* {
    deny all;
    access_log off;
    log_not_found off;
}

  add_header Content-Security-Policy upgrade-insecure-requests;

  ssl_certificate /etc/nginx/ssl/ur.pem;
  ssl_certificate_key /etc/nginx/ssl/ur.key;
#  ssl_dhparam /etc/nginx/ssl/dhparams.pem;
  ssl_session_timeout 5m;
  ssl_session_cache shared:SSL:5m;


  #SSL Security
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
  #XP and IE6 support
  #ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
  ssl_ecdh_curve secp384r1;
  ssl_prefer_server_ciphers on;
  ssl_session_tickets off;

  proxy_set_header X-Forwarded-For $remote_addr;
  
  #Compress and optimize delivery of files


  gzip on;
  gzip_comp_level    5;
  gzip_min_length    256;
  gzip_vary          on;
  gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/ld+json
    application/manifest+json
    application/rss+xml
    application/vnd.geo+json
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/bmp
    image/svg+xml
    image/x-icon
    text/cache-manifest
    text/css
    text/plain
    text/vcard
    text/vnd.rim.location.xloc
    text/vtt
    text/x-component
    text/x-cross-domain-policy;
    # text/html is always compressed by gzip module

}
nginx -s reload