> For the complete documentation index, see [llms.txt](https://endpoint-docs.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://endpoint-docs.gitbook.io/docs/novyi-server.md).

# Новый сервер

> В работе: на данный момент реализован основной функционал, в процессе последующей разработки будут добавляться новые возможности и меняться уже существующие.

## Техническая информация

Из‑за возросшей нагрузки и прочих факторов мы были вынуждены перейти с shared‑хостинга на VDS‑хостинг. Это означает, что стабильность приложений теперь целиком лежит на нас.

**Для удобства поиска необходимой информации:**

* [Как загрузить свое приложение на сервер?](#princip-sozdaniya-i-podnyatiya-bitrix-prilozheniya-na-servere)
* [Что нужно помнить при переносе приложений на новый сервер?](#vazhnye-momenty-pri-perenose-prilozhenii-so-starogo-servera)
* [Что находится внутри compose файлов?](#pro-docker-compose.yml-i-dockerfile)
* [Как работать с бд?](#rabota-s-bazoi-dannykh)
* [Как работать с cron?](#rabota-s-cron)
* [Как мне разрабатывать приложение?](#lokalnyi-deploi-dlya-razrabotki)

В качестве архитектуры сервера был выбран следующий подход:

#### Веб‑сервер: **Traefik**

**Traefik** — это **reverse proxy** и **load balancer**, который автоматически обнаруживает сервисы в **Docker** и настраивает маршрутизацию по доменам без ручной конфигурации.

У **Traefik** настроена автоматическая выдача **SSL‑сертификатов Let's Encrypt**; он прослушивает все **Docker‑контейнеры** и занимается роутингом по поддоменам. Как это работает:

* Traefik подхватывает этот контейнер и указанный URL, после чего поднимает поддомен.
* Когда приходит запрос на поддомен, Traefik принимает его и проксирует в нужный Docker‑контейнер.
* Traefik автоматически подхватывает контейнеры и их метки, а также выпускает SSL через Let's Encrypt без ручной настройки.
* Поднимается Docker‑контейнер, в файл Compose для него указываются метки:

```
            labels:
                traefik.enable=true
                traefik.http.routers.${{ env.APP_NAME }}-http.rule=Host(\`${{ env.APP_NAME }}.${{ env.DOMAIN }}\`)"
                traefik.http.routers.${{ env.APP_NAME }}-http.entrypoints=web"
                traefik.http.routers.${{ env.APP_NAME }}-http.middlewares=${{ env.APP_NAME }}-redirect"
                traefik.http.middlewares.${{ env.APP_NAME }}-redirect.redirectscheme.scheme=https"
                traefik.http.routers.${{ env.APP_NAME }}-https.rule=Host(\`${{ env.APP_NAME }}.${{ env.DOMAIN }}\`)"
                traefik.http.routers.${{ env.APP_NAME }}-https.entrypoints=websecure"
                traefik.http.routers.${{ env.APP_NAME }}-https.tls=true"
                traefik.http.routers.${{ env.APP_NAME }}-https.tls.certresolver=letsencrypt"
                traefik.http.services.${{ env.APP_NAME }}.loadbalancer.server.port=80"
                traefik.docker.network=traefik"

            networks:
                - traefik # внешняя сеть
                - app_net # внутренняя сеть сервисов контейнера
```

* Traefik подхватывает этот контейнер и указанный URL, после чего поднимает поддомен.
* Когда приходит запрос на поддомен, Traefik принимает его и проксирует в нужный Docker‑контейнер.

#### CP (панель управления сервером): aaPanel

aaPanel — это бесплатная веб‑панель управления сервером, упрощающая установку LAMP/LNMP, администрирование сайтов/БД/FTP, резервное копирование и базовую безопасность.

Основные вкладки CP:

* Главная — основная статистика по серверу.
* Docker — информация по всем контейнерам и управление ими.
* Мониторинг — сведения о нагрузке.

#### Контейнеры: **Docker**

**Docker‑контейнер** — это изолированный процесс с собственным файловым окружением, собранным из образа, который включает приложение и его зависимости. Контейнеры запускаются быстро, воспроизводимы и переносимы между серверами, при этом потребляют меньше ресурсов, чем виртуальные машины.

Каждое приложение работает в **Docker‑контейнере**. Файлы **docker-compose.yml** и **Dockerfile** генерируются автоматически при каждом пуше на **GitHub**. Ниже — основные команды для работы с контейнерами **Docker**:

#### Основные команды Docker

* Список контейнеров (запущенные):

```bash
docker ps
```

* Список всех контейнеров (включая остановленные):

```bash
docker ps -a
```

* Остановка / запуск / перезапуск контейнера:

```bash
docker stop myapp
docker start myapp
docker restart myapp
```

* Удаление контейнера:

```bash
docker rm myapp
docker rm -f myapp
```

* Просмотр логов:

```bash
docker logs myapp
docker logs -f myapp
```

* Открыть shell внутри контейнера:

```bash
docker exec -it myapp bash
```

* Выполнить команду внутри контейнера:

```bash
docker exec myapp php -v
```

* Информация о контейнере / порты / процессы:

```bash
docker inspect myapp
docker port myapp
docker top myapp
```

* Копирование файлов между хостом и контейнером:

```bash
docker cp myapp:/path/in/container ./local_dir
docker cp ./local_file myapp:/path/in/container
```

Образы и сборка

* Список образов / загрузка / удаление:

```bash
docker images
docker pull nginx:latest
docker rmi nginx:latest
```

* Сборка образа из Dockerfile в текущей папке:

```bash
docker build -t myapp:latest .
```

Ресурсы и очистка

* Мониторинг ресурсов контейнеров:

```bash
docker stats
```

* Очистка неиспользуемых ресурсов:

```bash
docker system prune -f
```

#### Docker Compose

* Запуск/остановка сервисов:

```bash
docker compose up -d
docker compose down
```

* Логи сервиса / команда внутри сервиса:

```bash
docker compose logs -f app
docker compose exec app bash
```

* Пересборка и перезапуск:

```bash
docker compose up -d --build
```

#### CI/CD и работа с автодеплоем приложений

**CI/CD** — это практика автоматизации сборки и тестирования кода (**CI**) и его доставки/развёртывания в среду (**CD**) при каждом изменении, чтобы быстрее и надёжнее выпускать обновления.

Для автоматизации деплоя в организации создан репозиторий под названием **.github**; в нём находятся два скрипта:

<figure><img src="/files/EYLvlQkR4YLeuNYhYZwK" alt=""><figcaption></figcaption></figure>

В папке **.github** находится workflow для CI/CD: файл **deploy.yml** отвечает за автоматическое развёртывание и настройку контейнеров и репозитория.

В папке **local\_deploy** находятся два файла для поднятия архитектуры приложения, подобной серверной, у себя на локальной машине для разработки.

### local\_deploy

> В разработке; некоторый функционал может работать некорректно.

Файлы, находящиеся в папке **local\_deploy** в репозитории **.github**, предназначены для разработки приложений на локальной машине.

В сущности, данный скрипт открывает **SSH‑туннель** до контейнера сервера, который поднимает этот туннель на поддомене домена **karos58.ru**. Как это работает:

* Скрипт формирует файлы **docker-compose** и **Dockerfile** для приложения.
* Скрипт поднимает контейнеры на локальной машине.
* Скрипт генерирует **SSH‑туннель** и ключ; ключ отправляется на микросервис контейнера **tunnel**, который регистрирует ключи в файле **.sshd**.
* После этого устанавливается связь с контейнером **tunnel‑endpoint**, который, в свою очередь, устанавливает связь с **nginx** и сопоставляет порты и поддомены.
* Приложение поднимается на поддомене **karos58.ru** с префиксом **app‑**.

## Практическая информация

### Принцип создания и поднятия Bitrix‑приложения на сервере

Разберём поэтапно, как загрузить приложение, предназначенное для публикации в Маркет или других целей.

### Первая загрузка

1. Для начала необходимо создать репозиторий с префиксом app‑ в репозиториях компании. Пошагово:

   * Переходим на сайт github.com, заходим в аккаунт и переходим на страницу компании Endpoint‑Web (вы добавлены туда мембером):

   <figure><img src="/files/vVcXCDlhibjQEBzgEklP" alt=""><figcaption></figcaption></figure>

   * Открываем вкладку Repositories и нажимаем кнопку New repository:

   <figure><img src="/files/lPbmyChYak23WzTOWQ1w" alt=""><figcaption></figcaption></figure>

   * Вводим название **обязательно** с префиксом app‑, то есть название репозитория должно выглядеть так: app‑<название\_приложения>. Если не ввести этот префикс, скрипт пропустит выполнение:

   <figure><img src="/files/xCqwPHNuzDz9PxI8lw3K" alt=""><figcaption></figcaption></figure>

   * Проверяем настройки:
     * Репозиторий должен быть приватным.
     * Остальное — на ваше усмотрение, но лучше добавлять README и .gitignore уже после первого деплоя.
   * Нажимаем кнопку Create repository.
2. Затем переходим в свой Git‑клиент (рекомендуется GitHub Desktop). Пошагово:

   * Клонируем репозиторий .github.

   <figure><img src="/files/3Ksmmldvh8nMbRktrr8H" alt=""><figcaption></figcaption></figure>

   * Клонируем только что созданный репозиторий (из пункта 1).

   <figure><img src="/files/mCktc2kR9mVlkhs05dgJ" alt=""><figcaption></figcaption></figure>

   * Из репозитория .github копируем папку .github в корень созданного репозитория (из пункта 1).

   <figure><img src="/files/kPCMrhoq7zFXbisOnpfI" alt=""><figcaption></figcaption></figure>

   * Добавляем свои файлы в созданный репозиторий (из пункта 1).
   * Добавляем необходимый .gitignore.
   * Делаем коммит и нажимаем кнопку Publish repository.

   <figure><img src="/files/Z9bgXHc4xUZ90rDcoEbc" alt=""><figcaption></figcaption></figure>
3. Возвращаемся на сайт github.com:

   * Открываем созданный репозиторий и переходим в раздел Actions:

   <figure><img src="/files/4AzA2dxSO689hzupntgc" alt=""><figcaption></figcaption></figure>

   * Ожидаем завершения workflow (иконка круга должна стать зелёной).

   <figure><img src="/files/weCeKW8yyOqCfPFxSWCM" alt=""><figcaption></figcaption></figure>

   * Нажимаем на название workflow.
   * Нажимаем кнопку Deploy.
   * Проверяем деплой (можно посмотреть все стадии выполнения и их выводы):

   <figure><img src="/files/LWnUaqqrSGYAoFvMfm3g" alt=""><figcaption></figcaption></figure>

   Чтобы узнать информацию о деплое:

   * Переходим в репозиторий и переключаемся на ветку deploy‑info:

   <figure><img src="/files/Vdc4fUFoFCme9Q4cfsyT" alt=""><figcaption></figcaption></figure>

   * Открываем файл DEPLOY.md и смотрим сведения о деплое:

   <figure><img src="/files/PgWjS4TuQkMZ0VLqLquW" alt=""><figcaption></figcaption></figure>

   Чтобы узнать пароли к БД и прочему:

   * Подключаемся к серверу любым способом (SSH, SFTP, FTP).
   * Открываем директорию приложения по пути /opt/apps/<название\_приложения>.
   * Открываем файл deploy-passwords.txt и смотрим пароли.

### Последующие загрузки

Разберём поэтапно, как сделать обновление или доработку приложения, репозиторий которого уже создан и задеплоен (см. раздел «Первая загрузка»).

1. Открываем GitHub Desktop:
   * Клонируем/пуллим нужный репозиторий.
   * Вносим необходимые изменения.
   * Делаем коммит и нажимаем кнопку Push.
2. Возвращаемся на сайт github.com:

   * Открываем нужный репозиторий и переходим в раздел Actions:

   <figure><img src="/files/DYUkHkeNDs1E0xAUnXUI" alt=""><figcaption></figcaption></figure>

   * Ожидаем завершения workflow (иконка круга должна стать зелёной).

   <figure><img src="/files/Agjv8R1W1z1J7qOSJ6kR" alt=""><figcaption></figcaption></figure>

   * Нажимаем на название workflow:
   * Нажимаем кнопку Deploy.
   * Проверяем деплой (можно посмотреть все стадии выполнения и их выводы):

   <figure><img src="/files/h50x3oxCMpc7JPWGPeFI" alt=""><figcaption></figcaption></figure>

   Чтобы узнать информацию о деплое:

   * Переходим в репозиторий и переключаемся на ветку deploy‑info:

   <figure><img src="/files/VViAXfsY2DjgfKJ4XJ8K" alt=""><figcaption></figcaption></figure>

   * Открываем файл DEPLOY.md и смотрим сведения о деплое:

   <figure><img src="/files/apMXOn2YQJyRZmkpE2TE" alt=""><figcaption></figcaption></figure>

   Чтобы узнать пароли к БД и прочему:

   * Подключаемся к серверу любым способом (SSH, SFTP, FTP).
   * Открываем директорию приложения по пути /opt/apps/<название\_приложения>.
   * Открываем файл deploy-passwords.txt и смотрим пароли.

### Работа с базой данных

Подключение к БД осуществляется через хост **db**, обязательно создавайте пользователя и базу данных, через pma, перед началом работы.

### Работа с cron

Для управления cron-задачами на контейнере, необходимо перейти по пути `https://<название-приложения>.end-point.ru/admin/cron` . После ввода авторизационных данных, откроется страница такого вида:<br>

<figure><img src="/files/4jF5wpSckoBjzzXNzjLV" alt=""><figcaption></figcaption></figure>

В данном интерфейсе, можно: добавить, изменить и включить/отключить cron-задачи.\
**Стоит помнить**, что путь до файлов приложения на сервере, и в контенере - различаются. В случае контейнера, путь до корня папки приложения будет выглядеть так: `/var/www/html/` . Именно такой путь необходимо указывать, если вы хотите исполнять какой-либо файл внутри директории приложения.

{% hint style="info" %}
**Важно!**\
По умолчанию, в любом приложение будет присутствовать стандартная cron-задача, для удаления CRest-логов. Не стоит пытаться удалить эту задачу.
{% endhint %}

### Локальный деплой (для разработки)

Для разработки приложений на локальной машине можно использовать файлы, находящиеся в папке **local\_deploy** в репозитории **.github**.

#### Windows:

Для начало работы, необходимо скачать с github компании репозиторий .github.

**Как пользоваться:**

1. Устанавливаем Docker Desktop(полностью, включая wsl).
2. Добавляем актуальные скрипты (**dev.ps1** и **dev.cmd**) из папки **`local_deploy/windows`** в корень папки, которую хотим поднять локально.
3. Открываем терминал и выполняем команду:

```powershell
.\dev.cmd up
```

4. Ожидаем выполнения скрипта; приложение будет доступно по адресу, указанному в выводе скрипта в терминале.

```powershell
Локальные URL:
  HTTPS: https://172.21.16.1:8081
  HTTP:  http://172.21.16.1:8080
Регистрация SSH-ключа на сервере...
Автоматическая регистрация ключа...
Ключ зарегистрирован

SSH туннель активен (PID: 19956, порт 10768):

URL приложения (туннель):
  https://dev-app-kurator.karos58.ru
```

**Все команды для работы со скриптом можно узнать, введя в терминале:**

```powershell
.\dev.cmd help
```

#### Доступные команды

**Поднять приложение:**

```powershell
.\dev.cmd up
```

**Остановить приложение:**

```powershell
.\dev.cmd down
```

**Посмотреть логи контейнеров:**

```powershell
.\dev.cmd logs
```

**Зайти в контейнер (доступ к терминалу):**

```powershell
.\dev.cmd sh
```

**Узнать URL приложения:**

```powershell
.\dev.cmd url
```

**Узнать пароли:**

```powershell
.\dev.cmd passwords
```

### Доступные опции запуска деплоя

Опции указываются после команды **up**:

```powershell
.\dev.cmd up -<опция>
```

**Список опций:**

```powershell
-db       # Поднятие приложения вместе с базой данных
-swagger  # Поднятие приложения вместе с Swagger (в разработке)
```

#### Linux:

**Как это работает:**

1. Устанавливаем docker([гайд](https://reg.cloud/support/cloud/oblachnyye-servery/ustanovka-programmnogo-obespecheniya/kak-ustanovit-docker-na-ubuntu?utm_source=google.com\&utm_medium=organic\&utm_campaign=reg.cloud\&utm_referrer=google.com\&utm_content=%2Fsupport%2Fcloud%2Foblachnyye-servery%2Fustanovka-programmnogo-obespecheniya%2Fkak-ustanovit-docker-na-ubuntu))
2. Добавляем актуальные скрипты (dev.sh) из папки **`local_deploy/Linux`** в корень папки, которую хотим поднять локально.
3. Открываем терминал и выполняем команду:

```powershell
./dev.sh up
```

4. Ожидаем выполнения скрипта; приложение будет доступно по адресу, указанному в выводе скрипта в терминале.

```powershell
Локальные URL:
  HTTPS: https://172.21.16.1:8081
  HTTP:  http://172.21.16.1:8080
Регистрация SSH-ключа на сервере...
Автоматическая регистрация ключа...
Ключ зарегистрирован

SSH туннель активен (PID: 19956, порт 10768):

URL приложения (туннель):
  https://dev-app-kurator.karos58.ru
```

**Все команды для работы со скриптом можно узнать, введя в терминале:**

```powershell
./dev.sh help
```

#### Доступные команды

**Поднять приложение:**

```powershell
./dev.sh up
```

**Остановить приложение:**

```powershell
./dev.sh down
```

**Посмотреть логи контейнеров:**

```powershell
./dev.sh logs
```

**Зайти в контейнер (доступ к терминалу):**

```powershell
./dev.sh sh
```

**Узнать URL приложения:**

```powershell
./dev.sh url
```

**Узнать пароли:**

```powershell
./dev.sh passwords
```

### Доступные опции запуска деплоя

Опции указываются после команды **up**:

```powershell
./dev.sh up -<опция>
```

**Список опций:**

```powershell
-db       # Поднятие приложения вместе с базой данных
-swagger  # Поднятие приложения вместе с Swagger (в разработке)
```

### Важные моменты

* Все временные файлы нужно добавлять в **.gitignore**, чтобы они не попадали в репозиторий.
* Все файлы настроек и конфигураций также добавлять в **.gitignore**.
* Нельзя менять исходный код приложения напрямую на сервере — все изменения вносятся через коммиты на **GitHub**.
* Все подключения к **БД** должны происходить через хост **db**, а не **localhost**.
* **Url путь** до файлов приложения выглядить вот так **https\://<название\_приложения>.end-point.ru/index.php**.
* Все логи в реальном времени выбрасываются из контейнера в корневую директорию приложения.
* Проверяйте репозиторий .github и при возможности выполняйте обновление своих workflow файлов в репозиториях.

### Важные моменты при переносе приложений со старого сервера

При переносе приложений стоит помнить про основные вещи, описанные ранее, разберем основные еще подробнее:

* Теперь каждое приложение имеет иной путь, а именно **<название-приложения>.end-point.ru/index.php**, если ваша страничка или хэндлер находится в какой-либо папке то путь будет выглятдеть так **<название-приложения>.end-point.ru/...(путь до файла)/handler.php** (*пример: **calls-manager.end-point.ru/templates/index.php**).*
* Все подключения к БД теперь происходят через host **db**, а не **localhost**

### Про docker-compose.yml и Dockerfile

При первом пуше в репозиторий в директории автоматически создаются **docker-compose.yml** и **Dockerfile**. Их настройки можно посмотреть в файле **deploy.yml** в репозитории **.github**.

> Важно: эти файлы генерируются автоматически — не редактируйте их вручную, без крайней необходимости. Разберём основные параметры этих файлов:

#### Сервис приложения

````yaml
  app:
    build: .
    container_name: testik
    command: ["bash","-lc","umask 000; /usr/sbin/cron || /usr/sbin/cron -f &>/dev/null & disown; exec /usr/sbin/apache2ctl -D FOREGROUND"]
    restart: unless-stopped
    environment:
      - APP_ENV=production
      - APP_NAME=testik
      - APP_HOST=testik.end-point.ru
      - DOMAIN=end-point.ru
      - DB_HOST=db
      - DB_PORT=****
      - DB_NAME=**************
      - DB_USER=**************
      - DB_PASS=******************
    depends_on:
      - db
    networks:
      - traefik
      - app_net
    expose:
      - "80"
    volumes:
      - ./:/var/www/html
      - cron_spool:/var/spool/cron/crontabs
      - cron_d:/etc/cron.d
    labels:
      - traefik.enable=true
      - traefik.http.routers.testik-http.rule=Host(`testik.end-point.ru`)
      - traefik.http.routers.testik-http.entrypoints=web
      - traefik.http.routers.testik-http.middlewares=testik-redirect
      - traefik.http.middlewares.testik-redirect.redirectscheme.scheme=https
      - traefik.http.routers.testik-https.rule=Host(`testik.end-point.ru`)
      - traefik.http.routers.testik-https.entrypoints=websecure
      - traefik.http.routers.testik-https.tls=true
      - traefik.http.routers.testik-https.tls.certresolver=letsencrypt
      - traefik.http.services.testik.loadbalancer.server.port=80
      - traefik.docker.network=traefik
    healthcheck:
      test: ["CMD-SHELL","curl -s -o /dev/null -w '%{http_code}' http://localhost/index.php | grep -E -q '^(2|3|4)[0-9]{2}'"]
      interval: 20s
      timeout: 5s
      retries: 10
      start_period: 15s

**У этого сервиса есть важные теги и переменные:**

```yaml 
restart: unless-stopped # контейнер перезапускается, если упал.
````

```yaml
environment # ключевые переменные окружения приложения: APP_ENV, APP_NAME, APP_HOST, DOMAIN, DB_HOST, DB_PORT и др.
```

```yaml
networks # сети, к которым подключён сервис (traefik, app_net).
```

```yaml
expose # порт, на котором отвечает приложение внутри сети (80).
```

```yaml
volumes # монтирование кода и cron‑томов (сохраняются между рестартами).
```

```yaml
labels # метки для Traefik (роутинг HTTP/HTTPS, certresolver, порт сервиса и т. п.).
```

**Важные теги и переменные:**

```yaml
restart: unless-stopped # автоматический перезапуск при падении.
```

```yaml
environment # параметры доступа к БД.
```

```yaml
volumes # том с данными БД (сохраняется между рестартами).
```

```yaml
networks # внутренняя сеть app_net.
```

### **Советы и правила по разработке приложений**

**Обязательно:**

* Добавлять конфиги создаваемые CRest в .gitignore
* Добавлять все логи в .gitignore
* Добавлять все config, settings, .env в .gitignore и создавать их в ручную на сервере

**Рекомендуется:**

* Хранить все уязвимые данные в файле .env
* Составлять url пути до хэндлеров, с помощью глобальных переменных и динамических путей.
* Подключать все классы через composer

##

## **Служебная информация**

*В это части будет изложена вся информация о архитектуре сервера и работе с ней.*&#x20;

### Структура сервера

Основные сервисы и их compose-файлы находятся в директории **/opt.**&#x20;

<figure><img src="/files/BYzCQTwRSjm60QbucmMm" alt=""><figcaption></figcaption></figure>

Для каждого нового сервиса необходимо создать новую директорию в **`/opt`**  с **понятным и читаемым** названием. **Допускается**, в случае необходимости, пробрасывать volume контейнера или какие-либо иные данные из директории **`/opt`**, в другие каталоги Linux.

### Существующие сервисы и работа с ними&#x20;

> Информация в этом блоке может меняться, в ходе разработки.

#### **apps**

Директория, в которой находятся все приложения компании, именно от сюда они монтируются на docker-контейнеры.

#### **db**

Центральный и главный сервер бд, вместе с ним, также поднимается и pma:

```yaml
version: "3.8"

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=************
      - MYSQL_DATABASE=*******
      - MYSQL_USER=********
      - MYSQL_PASSWORD=***********
    command: ["--character-set-server=utf8mb4","--collation-server=utf8mb4_unicode_ci","--skip-name-resolve","--default-authentication-plugin=mysql_native_password"]
    healthcheck:
      test: ["CMD-SHELL","mysqladmin ping -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD --silent || exit 1"]
      interval: 5s
      timeout: 5s
      retries: 120
      start_period: 30s
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - db

  phpmyadmin:
    image: phpmyadmin:5-apache
    container_name: pma
    restart: unless-stopped
    depends_on:
      - db
    environment:
      - PMA_HOST=db
      - PMA_PORT=3306
      - PMA_ARBITRARY=0
      - UPLOAD_LIMIT=256M
      - APACHE_PORT=80
    ports:
      - 29000:80
    networks:
      - db

networks:
  db:
    name: db

volumes:
  db_data:
```

**services.db**\
Контейнер MySQL 8.0. Указаны пароли и название базы. Настроены параметры кодировки и авторизации. Хелсчек проверяет, что MySQL запустился. Данные базы хранятся в volume `db_data`.

**services.phpmyadmin**\
Веб-панель для управления MySQL. Подключается к сервису `db`. Установлено ограничение на размер загружаемых файлов.

**networks.db**\
Отдельная сеть, в которой работают MySQL и phpMyAdmin.

**volumes.db\_data**\
Папка для постоянного хранения данных MySQL.

#### **github-runner**

Директория с рабочими файлами self-hosted github-runner. Необходимая информация о нем находится в [официальной документации](https://docs.github.com/en/actions/reference/runners/self-hosted-runners).  Раннер - один из немногих компонентов установленный на host-машину, установка runner на докер сопряжена с некоторыми трудностями, потому было принято такое решение. &#x20;

**Основные команды раннера:**

Запуск раннера

```bash
cd /opt/github-runner
./run.sh
```

Запуск как сервис

```bash
./svc.sh install
./svc.sh start
./svc.sh status
```

Остановка сервиса

```bash
./svc.sh stop
```

Удаление сервиса

```bash
./svc.sh uninstall
```

Удаление регистрации раннера

```bash
cd /opt/github-runner
./config.sh remove
```

Очистка workspace (если что-то зависло)

```bash
rm -rf /opt/github-runner/_work/*
rm -rf /opt/github-runner/_diag/*
```

#### **sites**

Контейнер для сайтов сделанных на wp и прочих cms. На данный момент на нем находится два сайта.

```yaml
version: "3.9"

services:
  wp-sites:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: wp-sites
    restart: always
    environment:
      - TZ=Europe/Moscow
      - MYSQL_ROOT_PASSWORD=****
      - FORCE_SSL_ADMIN=true
    volumes:
      - ./sites:/var/www/html
      - db_data:/var/lib/mysql
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      
      # HTTP роутер для geologics.ru
      - "traefik.http.routers.geologics-http.rule=(Host(`geologics.ru`) || Host(`www.geologics.ru`))"
      - "traefik.http.routers.geologics-http.entrypoints=web"
      - "traefik.http.routers.geologics-http.middlewares=geologics-redirect"
      - "traefik.http.middlewares.geologics-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.geologics-redirect.redirectscheme.permanent=true"
      
      # HTTPS роутер для geologics.ru
      - "traefik.http.routers.geologics-https.rule=(Host(`geologics.ru`) || Host(`www.geologics.ru`))"
      - "traefik.http.routers.geologics-https.entrypoints=websecure"
      - "traefik.http.routers.geologics-https.tls=true"
      - "traefik.http.routers.geologics-https.tls.certresolver=letsencrypt"
      - "traefik.http.routers.geologics-https.service=wp-sites"

        # HTTP роутер для end-point.ru
      - "traefik.http.routers.end-point-http.rule=(Host(`end-point.ru`) || Host(`www.end-point.ru`))"
      - "traefik.http.routers.end-point-http.entrypoints=web"
      - "traefik.http.routers.end-point-http.middlewares=end-point-redirect"
      - "traefik.http.middlewares.end-point-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.end-point-redirect.redirectscheme.permanent=true"
      
      # HTTPS роутер для end-point.ru
      - "traefik.http.routers.end-point-https.rule=(Host(`end-point.ru`) || Host(`www.end-point.ru`))"
      - "traefik.http.routers.end-point-https.entrypoints=websecure"
      - "traefik.http.routers.end-point-https.tls=true"
      - "traefik.http.routers.end-point-https.tls.certresolver=letsencrypt"
      - "traefik.http.routers.end-point-https.service=wp-sites"
      
      - "traefik.http.services.wp-sites.loadbalancer.server.port=80"
    networks:
      - traefik

volumes:
  db_data:

networks:
  traefik:
    external: true
```

**services.wp-sites**\
Контейнер с WordPress, собирается из локального `Dockerfile`. Установлены переменные окружения: часовой пояс, root-пароль MySQL и принудительное использование SSL для админки.\
Монтируются локальные папки с сайтами (`./sites`) и volume для данных MySQL (`db_data`).\
Через labels настроена маршрутизация и поддержка HTTPS с TLS (Let's Encrypt).

**volumes.db\_data**\
Хранение данных MySQL, чтобы база сохранялась при перезапуске контейнера.

**networks.traefik**\
Используется внешняя сеть Traefik для маршрутизации и балансировки нагрузки.

Для установки новых сайтов необходимо положить исходники сайта в директорию sites, перетянуть дамп сайта в бд контейнера, добавить labels по примеру, остальных сайтов и поднять контейнер.

#### **traefik**

Основной реверс-прокси сервера, на него приходят все запросы, которые он потом распределяет по контейнерам. Traefik был выбран в пользу классического nginx, за счет его превосходной работы с контейнерной архитектурой.

**Compose:**&#x20;

```yaml
services:
  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    command:
      - --configFile=/etc/traefik/traefik.yml
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"   # dashboard/API
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /opt/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
      - /opt/traefik/dynamic:/dynamic:ro
      - /opt/traefik/acme.json:/acme.json
      - /opt/traefik/logs:/var/log
    networks:
      - traefik

networks:
  traefik:
    external: true
```

**Config(traefik.yml):**

```yaml
global:
  checkNewVersion: false
  sendAnonymousUsage: false

api:
  dashboard: true
  insecure: true

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entrypoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik
    watch: true
  file:
    directory: /dynamic
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: support@gkyw.ru
      storage: /acme.json
      keyType: EC256
      httpChallenge:
        entryPoint: web

log:
  level: DEBUG
  filePath: "/var/log/traefik.log"

accessLog:
  filePath: "/var/log/access.log"

metrics:
  prometheus:
    addEntryPointsLabels: true
    addServicesLabels: true

```

**services.traefik**\
Контейнер Traefik v2.10 для маршрутизации и управления HTTPS. Настроен с указанием конфигурационного файла (`traefik.yml`), проброшенными портами 80 (HTTP), 443 (HTTPS) и 8080 (dashboard/API).\
Монтируются: Docker сокет для автоматического обнаружения контейнеров, конфигурационные файлы, динамические правила, файл хранения сертификатов и лог-файлы. Подключён к внешней сети `traefik`.

**traefik.yml (конфигурация)**

* **global** — отключена проверка новых версий и сбор анонимной статистики.
* **api** — включён dashboard и API (insecure).
* **entryPoints** — HTTP перенаправляется на HTTPS, настроен HTTPS-порт.
* **providers** — Docker (отслеживание контейнеров на сети `traefik`) и файловый (динамические правила из папки `/dynamic`).
* **certificatesResolvers** — автоматическое получение TLS-сертификатов через Let's Encrypt с HTTP-challenge.
* **log и accessLog** — хранение логов в указанных файлах, уровень DEBUG.
* **metrics** — Prometheus-метрики с привязкой к точкам входа и сервисам.

**networks.traefik**\
Используется внешняя сеть Traefik для маршрутизации контейнеров.

В сущности, это стандартная настройка traefik, работа с traefik очень проста и позволяет снять с себя, очень много головной боли. Всю необходимую информацию о traefik, можно найти в [официальной документации](https://doc.traefik.io/traefik/).

#### **Работа с основными инструментами сервера и их настройка**

Многие сервисы, описанные выше, также имеют интерфейс, в котором требуется настройка, разберем каждый:

\
**Backrest:**

Удобный и простой инструмент для бэкапов, на данный момент бэкапы хранятся на самом сервере. \
После захода в админку backerst, можно увидеть такую страницу &#x20;

<figure><img src="/files/ZkiWgPeJWLCOqJwnoZOj" alt=""><figcaption></figcaption></figure>

В backrest есть **две сущности**:<br>

* Репозитории - место хранения резервных копий. может быть локальным, на сетевом диске или в облачном хранилище. содержит все бэкапы, логи и метаданные для восстановления.
* План - набор настроек для создания резервной копии конкретных данных. определяет, что и куда бэкапить, частоту, а также типы резервных копий (полные, инкрементальные, дифференциальные).

На сервере настроены бэкапы для всех основных данных. Подробнее о backrest можно узнать в [официальной документации](https://garethgeorge.github.io/backrest/introduction/getting-started/).

aaPanel:

Основная CP сервера, основная задача это отслеживания нагрузки и управление контейнерами docker. \
После перехода в админку, можно увидеть такую страницу:<br>

<figure><img src="/files/ILvwpfMHh8nusetl9p7n" alt=""><figcaption></figcaption></figure>

Тут есть все необходимые метрики для просмотра нагрузки и прочего, также есть вкладка для управления контейнерами сервера под названием **Docker.**

<figure><img src="/files/dWWuMny05ul6CVO0Q75K" alt=""><figcaption></figcaption></figure>

По большому счету, основная цель CP, вести статистику нагрузки на сервер, пользоваться через нее админскими функциями не очень удобно и практично.

#### Docker и CI/CD

Основной функционал сервера - это хостинг приложений для маркетплейса. Все приложения лежат на гитхабе компании. Для всех репозиториев приложений создан workflow файл с пайплайном для автоматической доставки исходного кода на сервер, с последующим деплоем. На данный момент, сама доставка реализована через работу с work\_tree пришедшего коммита, данный функционал был реализован из-за невозможности, в текущее время, настроить secrets на репозиториях github компании. Для работы с пайплайном  используется поднятый на сервере self-hosted github-runner, методы работы с которым, были описаны выше. После доставки исходного кода на сервер, создаются/обновляются или остаются прежним docker-compose и docker файлы. Примерное наполнение этих файлов:

docker-compose:

```yaml
services:
  app:
    build: .
    container_name: userfieldshistory_widget
    command: ["/usr/local/bin/bootstrap-docroot.sh"]
    restart: unless-stopped
    cap_add:
      - SYS_PTRACE
      - SYS_ADMIN
    environment:
      - TZ=Europe/Moscow
      - APP_ENV=production
      - APP_NAME=userfieldshistory-widget
      - APP_HOST=userfieldshistory-widget.end-point.ru
      - DOMAIN=end-point.ru
      - DB_HOST=db
      - DB_PORT=3306
    networks:
      - traefik
      - db
    expose:
      - "80"
    volumes:
      - ./:/var/www/html
      - cron_spool:/var/spool/cron/crontabs
      - cron_d:/etc/cron.d
    labels:
      - traefik.enable=true
      - traefik.http.routers.userfieldshistory-widget-http.rule=Host(`userfieldshistory-widget.end-point.ru`)
      - traefik.http.routers.userfieldshistory-widget-http.entrypoints=web
      - traefik.http.routers.userfieldshistory-widget-http.middlewares=userfieldshistory-widget-redirect
      - traefik.http.middlewares.userfieldshistory-widget-redirect.redirectscheme.scheme=https
      - traefik.http.routers.userfieldshistory-widget-https.rule=Host(`userfieldshistory-widget.end-point.ru`)
      - traefik.http.routers.userfieldshistory-widget-https.entrypoints=websecure
      - traefik.http.routers.userfieldshistory-widget-https.tls=true
      - traefik.http.routers.userfieldshistory-widget-https.tls.certresolver=letsencrypt
      - traefik.http.services.userfieldshistory-widget.loadbalancer.server.port=80
      - traefik.docker.network=traefik
networks:
  traefik:
    external: true
  db:
    external: true
volumes:
  cron_spool:
  cron_d:

```

**services.app**\
Контейнер приложения `userfieldshistory-widget`, собирается из локального Dockerfile. запускается с командой `bootstrap-docroot.sh`.\
установлены переменные окружения для часового пояса, окружения приложения, имени, хоста и подключения к базе данных.\
контейнер подключен к сетям `traefik` и `db`, порт 80 выставлен через `expose`.\
смонтированы локальные каталоги с кодом и cron-таблицами.\
через labels настроена маршрутизация и поддержка HTTPS с TLS (Let's Encrypt) для Trae

**volumes.cron\_spool / volumes.cron\_d**\
Постоянное хранение cron-заданий и конфигураций.

networks.traefik/networks.db

Подключение к сети traefik и к сети базы данных.

Dockerfile:<br>

```bash
FROM php:8.1-apache
ARG CACHE_BUST=initial
RUN echo "cache-bust=$CACHE_BUST" >/dev/null
ENV TZ=Europe/Moscow
ENV APACHE_RUN_USER=www-data
ENV APACHE_RUN_GROUP=www-data
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN echo "umask 000" >> /etc/bash.bashrc && echo "umask 000" >> /etc/profile

RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libzip-dev \
    libicu-dev \
    libonig-dev \
    libxml2-dev \
    default-mysql-client \
    unzip \
    git \
    curl \
    nano \
    cron \
    tzdata \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        gd \
        mysqli \
        pdo_mysql \
        zip \
        intl \
        mbstring \
        opcache \
        xml \
        soap \
        pcntl \
    && rm -rf /var/lib/apt/lists/*

RUN echo "date.timezone = Europe/Moscow" > /usr/local/etc/php/conf.d/timezone.ini
RUN a2enmod rewrite headers ssl
RUN printf "ServerName localhost\n" > /etc/apache2/conf-available/servername.conf && a2enconf servername
RUN echo "umask 000" >> /etc/apache2/envvars

RUN printf '%s\n' \
'#!/usr/bin/env bash' \
'set -e' \
'umask 000' \
'chmod -R 777 /var/www/html 2>/dev/null || true' \
'DOCROOT=/var/www/html' \
'CANDIDATES="/var/www/html/public /var/www/html/web /var/www/html/dist /var/www/html/app"' \
'FOUND=""' \
'for d in $CANDIDATES; do' \
'  if [ -f "$d/index.php" ]; then' \
'    FOUND="$d"' \
'    break' \
'  fi' \
'done' \
'if [ -z "$FOUND" ]; then' \
'  CAND=$(find "/var/www/html" -maxdepth 3 -type f -name index.php 2>/dev/null | head -n1)' \
'  if [ -n "$CAND" ]; then' \
'    FOUND=$(dirname "$CAND")' \
'  fi' \
'fi' \
'[ -z "$FOUND" ] && FOUND="/var/www/html"' \
'if [ "$FOUND" != "/var/www/html" ]; then' \
'  if [ ! -f "/var/www/html/index.php" ] || grep -q "AUTO-GENERATED BOOTSTRAP" "/var/www/html/index.php" 2>/dev/null; then' \
'    cat > "/var/www/html/index.php" <<EOPHP' \
'<?php /* AUTO-GENERATED BOOTSTRAP */' \
'$target = __DIR__ . substr("$FOUND", strlen("/var/www/html")) . "/index.php";' \
'if (!is_file($target)) { http_response_code(500); echo "Index not found"; exit; }' \
'require $target;' \
'EOPHP' \
'    chmod 777 "/var/www/html/index.php" 2>/dev/null || true' \
'    chown www-data:www-data "/var/www/html/index.php" 2>/dev/null || true' \
'  fi' \
'fi' \
'service cron start >/dev/null 2>&1 || /usr/sbin/cron >/dev/null 2>&1 || true' \
'exec apache2-foreground' \
> /usr/local/bin/bootstrap-docroot.sh
RUN chmod +x /usr/local/bin/bootstrap-docroot.sh


COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
CMD ["/usr/local/bin/bootstrap-docroot.sh"]

```

**Установка зависимостей**\
устанавливаются необходимые пакеты для PHP (gd, mysqli, pdo\_mysql, zip, intl, mbstring, opcache, xml, soap, pcntl), клиент MySQL, утилиты (`git`, `curl`, `nano`, `unzip`, `cron`) и настройки таймзоны.

**Настройка PHP и Apache**

* задаётся `date.timezone` (`Europe/Moscow`)
* включаются модули Apache: `rewrite`, `headers`, `ssl`
* конфигурируется `ServerName localhost`
* устанавливается `umask 000` для корректных прав доступа

После создания файлов выполняются команды для поднятия контейнера, дампа autoload composer-a и тп. \
В конечном итоге, упрощенно, архитектуру сервера можно изобразить вот-так:

<figure><img src="/files/l5BzcECQSxXWZtaiDBio" alt=""><figcaption></figcaption></figure>
