Stan's blog

Docker Linux Gitlab CI/CD Laravel

Выкатка (Deploy) Приложения Laravel через gitlab CI/CD с использованием Deployer. Все размещено в Docker.

3 августа 2025
AI Generation

Описание конфигурации

На сервере, куда надо произвести deploy, в контейнере выполняется Gitlab Runner в режиме docker.
Когда нужно произвести deploy, Gitlab Runner запускает специально подготовленный образ на основе Deployer, в который установлены: node.js и клиент для базы (в моем случае Mariadb). Внутрь этого образа проброшена папка с проектом на хосте, таким образом не надо использовать ssh вообще. Скрипт Deplyer-а после всех операций по сборке проекте, ассетов, очистки кэша и пр. перезапускает контейнер с php сервером.

Сложность этой конфигурации заключается только в том, что с начал надо собрать сборочный образ с Deployer и аккуратно пробросить директории внутрь. Все.

Далее идет план развертывания и файлы конфигурации.

План развертывания:

  1. 1 - В проекте настроить .gitlab-ci.yml и deploy.php
  2. 2 - добавить пользователя, который будет запускать докер в группу docker
  3. 3 - создать папку my_app/deploy/ в папке пользователя.
  4. 4 - создать файл docker-compose.yml
  5. 5*(опционально) - создать  gitlab-runner/config/certs/ca.crt если надо добавить корневой сертификат
  6. 6 - Запускаем
docker compose run gitlab-runner register
И регистрируем в gitlab в проекте! runner тэг deploy с типом executor = docker (последнее указывается в консоли)  
  1. 7 - создаём deployer-with-node/Dockerfile
  2. 8 - в папке deployer-with-node выполняем
docker build -t deployer-with-node .
  1. 9 - обновляем конфигурацию gitlab-runner, меняем gitlab-runner/config/config.toml
  2. 10 - создать /nginx/default.conf
  3. 11*(опционально) - создать другие конфигурационные файлы если надо
  4. 12 - запускаем все контейнеры
docker compose up -d
Это может занять некоторое время пока все скачается.
  1. 13 - настроить базу 
docker compose exec mariadb mariadb -p
И далее создать пользователя, дать права, создать базу если надо...
  1. 14 - Настроить .env для проекта (создать папку my_app/shared)
vim /var/www/my_app/shared/.env
  1. 15 - запустить выкатку в gitlab

Файлы конфигурации

Файлы в корне проекта
Конфигурация CI/CD.
.gitlab-ci.yml
stages:
  - deploy

deploy:
  stage: deploy
  tags:
    - deploy
  when: manual # - Запуск вручную. 
  image:
    name: deployer-with-node
    entrypoint: [""]
  script:
    - dep deploy -vvv
  resource_group: production
Конфигурация Deployer
deploy.php
<?php
namespace Deployer;

require 'recipe/laravel.php';

// Project name
set('application', 'my_app');
set('keep_releases', 5);
set('deploy_path', '/var/www/{{application}}');
set('http_user', '33'); // !!! 33 это UID www-data на хосте, куда идет выкатка. Может не совпадать с UID в контейнере, поэтому указано цифрой!
set('http_group', '33');

set('writable_mode', 'chown');
set('writable_recursive', true);

add('shared_files', [ ".env" ]);

localhost();

task('deploy:update_code', function () {
    writeln("<info>Uploading files to server</info>");
    upload('./', '{{release_path}}');
});

task('build:front', function () {
    run('cd {{release_path}} && npm install && npm run build');
});

task('reload', function () {
    // Перезапуск контейнера с сервером!
    run('docker compose -f /home/user/my_app/deploy/docker-compose.yml restart my_app-php-fpm nginx');
});

// если права на доступ не прописаны...
task('chown', function () {
    run('chown -R 33:33 {{deploy_path}}');
});

task('deploy', [
  'deploy:info',          // Выводит информацию о процессе сборки
  'deploy:setup',         // Проверяет наличие служебных структур, необходимых для работы системы сборки и доставки
  'deploy:lock',          // Блокирует одновременный запуск нескольких процессов сборки и доставки
  'deploy:release',       // создает каталоги для нового релиза 
  'deploy:update_code',   // Обновляет репозиторий с кодом в каталоге репозитория 
  'deploy:shared',        // Создает в репозитории общие каталоги и файлы (которые не хранятся в репозитории) 
  'deploy:writable',      // Создает каталоги, которые должны быть доступны для записи в месте деплоя 
  'deploy:vendors',       // Собирает все зависимости 
  'build:front',          // Сборка ассетов для nodejs 
  'artisan:storage:link', // Создает ссылку на каталог для хранения данных 
  'artisan:config:cache', // Обновляет кеш с конфигурацией  
  'artisan:route:cache',  // Обновляет кеш с маршрутами для URL запросов 
  'artisan:migrate',      // Выполняет миграции над базой  
  'deploy:symlink',       // Устанавливает ссылку current на новую версию репозитория  
  'deploy:unlock',        // Разблокирует релиз 
  'deploy:cleanup',       // Удаляет старые релизы и ссылку release 
  'deploy:success',       // Пишет, что все прошло отлично 
  // 'chown',                // Поменять права доступа на папки (возможно лишнее)  
  'reload',               // рестарт контейнеров 
])->desc('Установка нового релиза для {{appliction}}');

// Hooks
after('deploy:failed', 'deploy:unlock');

Файлы на сервере куда производится выкатка
Структура папок:
/home/user/my_app/deploy/
  data/                   - mariadb base, создаётся автоматически 
  deployer-with-node/  
    Dockerfile            - Конфигурация на основе Deployer с node и mariadb-client 
  gitlab-runner/
    config/
      certs/
         ca.crt           - Если у вас самоподписанный сертификат у gitlab, то сюда разместите корневой сертификат для него.
      config.toml         - Конфигурация gitlab runner!
  my_app-php-fpm/
    Dockerfile            - конфигурация образа PHP сервера со всеми дополнительными пакетами.
    php.ini               - опционально можно разместить свои настройки
    www.conf              - опционально можно разместить свои настройки
  mariadb/
    50-server.cnf         - Конфигурация для сервера MariaDB
  nginx/
    default.conf          -
  nginx-log/              - создаётся автоматически 
  docker-compose.yml      - Конфигурация всех сервисов

Конфигурация всех сервисов

  docker-compose.yml
version: "3.3"

services:
  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    depends_on:
      - mariadb
    networks:
      my_app:
      front:
    container_name: gitlab-runner
    restart: always
    volumes:
      - ./gitlab-runner/config/:/etc/gitlab-runner/ # конфигурация
      - /var/run/docker.sock:/var/run/docker.sock   # чтобы управлять докером из докера
      - /var/www:/var/www # !!! папка с проектом 
      - /home/user/my_app/deploy:/home/user/my_app/deploy:ro # !!! путь к этомой конфигурации, 
                                  # чтобы в докере в докере перезапустить контейнер с сервером
    #extra_hosts:
      # - "gitlab.myhost.ru:15.200.2.12" - если надо указать ip где гитлаб

  # можно в /etc/sysctl.conf добавить vm.overcommit_memory для mariadb
  # host sudo nano /etc/sysctl.conf | vm.overcommit_memory = 1 | sudo sysctl -p
  mariadb:
    image: 'mariadb:11.8'
    container_name: mariadb
    restart: unless-stopped
    networks:
      front:
        ipv4_address: 172.17.254.253 # чтобы достучаться из докера в докере
    volumes:
      - ./data/mysql/mariadb-11.8:/var/lib/mysql
      - ./data/mysql/backup:/backup
      #- type: bind  - если надо переписать настройки сервера
      #  source: ./mariadb/50-server.cnf
      #  target: /etc/mysql/mariadb.conf.d/50-server.cnf
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: my_app_dev 
      MYSQL_USER: user
      MYSQL_PASSWORD: pass
    healthcheck:
      test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ]
      interval: 5s
      timeout: 3s
      retries: 10

  my_app-php-fpm: # контейнер с PHP сервером
    container_name: my_app-php-fpm
    build:
      context: ./my_app-php-fpm
    restart: unless-stopped
    networks:
      my_app:
        ipv4_address: 172.17.250.254
      front:
    depends_on:
      - mariadb
    volumes:
      # - type: bind      - опционально, если надо прокинуть свой конфиг
      #   source: ./my_app-php-fpm/www.conf
      #   target: /usr/local/etc/php-fpm.d/www.conf
      # - type: bind
      #  source: ./my_app-php-fpm/php.ini
      #  target: /usr/local/etc/php/php.ini
      - /var/www:/var/www # !!!  

  nginx: # веб сервер. Можно настроить на 443, можно вместо него использовать tareafik или apache...
    image: nginx:alpine
    container_name: nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf # конфигурация сервера
      - /var/www/my_app/current/public:/var/www/my_app/current/public 
      - ./nginx-log:/var/log/nginx
    depends_on:
      - my_app-php-fpm
    networks:
      - front

  redis: # если нужен конечно
    image: 'redis:alpine'
    volumes:
      - 'redis-data:/data'
    networks:
      - my_app
    healthcheck:
      test:
        - CMD
        - redis-cli
        - ping
      retries: 3
      timeout: 5s

networks:
  my_app:
    ipam:
      config:
        - subnet: 172.17.250.0/24
  front:
    ipam:
      config:
        - subnet: 172.17.254.0/24

volumes:
  redis-data:
    driver: local

Конфигурация образа Deployer

deployer-with-node/Dockerfile
# Назвать deployer-with-node, именно это прописано в gitlab runner config.toml
# Используется внутри gitlab runner для выкатки проекта
# Собирать на машине, где будет выкатка
# docker build -t deployer-with-node .

FROM deployphp/deployer:v7

# Install dependencies for Node.js and build tools
RUN apk add --no-cache \
    curl \
    python3 \
    make \
    g++

# Install Node.js 22 using Alpine's package manager (simple but may not be latest 22.x)
RUN apk add --no-cache nodejs npm

# OR for exact Node.js 22 version (better approach):
RUN curl -fsSL https://unofficial-builds.nodejs.org/download/release/v22.17.1/node-v22.17.1-linux-x64-musl.tar.xz | tar -xJ -C /usr --strip-components=1 --no-same-owner

RUN echo $(less /etc/apk/repositories)

RUN apk add --no-cache docker docker-compose

# Verify Docker Compose
RUN docker compose version

RUN apk add --no-cache mariadb-client

RUN mysql --version

RUN docker-php-ext-install pdo pdo_mysql mysqli

RUN rm -rf /var/cache/apk/*

# Verify installations
RUN node --version && npm --version && docker --version

Конфигурация Gitlab Runner (выполняется после привязки)
gitlab-runner/config/config.toml
# Конфигурация gitlab-runner для закуска деплойера

concurrent = 1
check_interval = 0
connection_max_age = "15m0s"
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "my_app"
  url = "https://gitlab"
  id = 8
  token = "glrt-oE4DcpFUWJ7GfD55eZIyV286MQpwOjYKdDozCnU6eQ8.01.171rj3au6"
  token_obtained_at = 2025-07-31T05:05:48Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.cache]
    MaxUploadedArchiveSize = 0
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "deployer-with-node" # !!! - указать какой образ брать
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache","/var/run/docker.sock:/var/run/docker.sock","/home/user/tools/ca.crt:/etc/gitlab-runner/certs/ca.crt:ro","/home/user/my_app/deploy:/home/user/my_app/deploy:ro","/var/www:/var/www"]
    shm_size = 0
    network_mtu = 0
    pull_policy = "never" # !!! - чтобы не пытался найти этот образ в реестре
    # extra_hosts = ["gitlab.myhost.ru:15.200.2.12"] #если надо указать ip
    network_mode = "host" 
Конфигурация nginx
/nginx/default.conf
server {
    listen 80 default_server;
    server_name _;
    root /var/www/my_app/current/public;
    index index.php;

    access_log /var/log/nginx/my_app-access.log;
    error_log /var/log/nginx/my_app-error.log;

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

    location ~ \.php$ {
        fastcgi_pass my_app-php-fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(ht|env) {
        deny all;
    }
}

Настройки .env

APP_NAME=MyApp
APP_ENV=production

APP_KEY=base64:somekey= # какой-то ключ - его надо сгенерировать
APP_DEBUG=false
APP_URL=http://my_app.ru


APP_LOCALE=ru
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file

PHP_CLI_SERVER_WORKERS=4

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug


DB_CONNECTION=mariadb
DB_HOST=172.17.254.253 # !!!
DB_PORT=3306
DB_DATABASE=my_app_db
DB_USERNAME=my_app_user
DB_PASSWORD=my_app_user_password

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database

CACHE_STORE=database

#MEMCACHED_HOST=127.0.0.1

REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

VITE_APP_NAME="${APP_NAME}"