Выкатка (Deploy) Приложения Laravel через gitlab CI/CD с использованием Deployer. Все размещено в Docker.
3 августа 2025
1.jpg)
Описание конфигурации
На сервере, куда надо произвести 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}"