Выкатка (Deploy) Приложения Laravel через gitlab CI/CD с использованием Deployer. Все размещено в Docker.
3 августа 2025
Описание конфигурации
На сервере, куда надо произвести deploy, в контейнере выполняется Gitlab Runner в режиме docker.
Когда нужно произвести deploy, Gitlab Runner запускает специально подготовленный образ на основе Deployer, в который установлены: node.js и клиент для базы (в моем случае Mariadb). Внутрь этого образа проброшена папка с проектом на хосте, таким образом не надо использовать ssh вообще. Скрипт Deplyer-а после всех операций по сборке проекте, ассетов, очистки кэша и пр. перезапускает контейнер с php сервером.
Сложность этой конфигурации заключается только в том, что с начал надо собрать сборочный образ с Deployer и аккуратно пробросить директории внутрь. Все.
Далее идет план развертывания и файлы конфигурации.
Когда нужно произвести deploy, Gitlab Runner запускает специально подготовленный образ на основе Deployer, в который установлены: node.js и клиент для базы (в моем случае Mariadb). Внутрь этого образа проброшена папка с проектом на хосте, таким образом не надо использовать ssh вообще. Скрипт Deplyer-а после всех операций по сборке проекте, ассетов, очистки кэша и пр. перезапускает контейнер с php сервером.
Сложность этой конфигурации заключается только в том, что с начал надо собрать сборочный образ с Deployer и аккуратно пробросить директории внутрь. Все.
Далее идет план развертывания и файлы конфигурации.
План развертывания:
- 1 - В проекте настроить .gitlab-ci.yml и deploy.php
- 2 - добавить пользователя, который будет запускать докер в группу docker
- 3 - создать папку my_app/deploy/ в папке пользователя.
- 4 - создать файл docker-compose.yml
- 5*(опционально) - создать gitlab-runner/config/certs/ca.crt если надо добавить корневой сертификат
- 6 - Запускаем
docker compose run gitlab-runner register
И регистрируем в gitlab в проекте! runner тэг deploy с типом executor = docker (последнее указывается в консоли)
- 7 - создаём deployer-with-node/Dockerfile
- 8 - в папке deployer-with-node выполняем
docker build -t deployer-with-node .
- 9 - обновляем конфигурацию gitlab-runner, меняем gitlab-runner/config/config.toml
- 10 - создать /nginx/default.conf
- 11*(опционально) - создать другие конфигурационные файлы если надо
- 12 - запускаем все контейнеры
docker compose up -d
Это может занять некоторое время пока все скачается.
- 13 - настроить базу
docker compose exec mariadb mariadb -p
И далее создать пользователя, дать права, создать базу если надо...
- 14 - Настроить .env для проекта (создать папку my_app/shared)
vim /var/www/my_app/shared/.env
- 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
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
/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}"