From e639968a46d05679f48af080840225cab637ecfe Mon Sep 17 00:00:00 2001 From: kosipov Date: Tue, 15 Sep 2026 15:39:41 +0300 Subject: [PATCH] Adapt deployment for Dokploy Replace the Gitea Actions SSH deploy with a Docker image (serversideup/php fpm-nginx) and docker-compose setup. Store the SQLite database inside the persistent storage volume, trust the reverse proxy, and move admin credentials into config so they work with cached configuration. Co-Authored-By: Claude Opus 5 --- .dockerignore | 24 +++++++ .env.example | 3 + .gitea/workflows/deploy.yml | 87 ----------------------- Dockerfile | 39 ++++++++++ README.md | 65 +++++++++++------ bootstrap/app.php | 3 + config/admin.php | 6 ++ docker-compose.yml | 15 ++++ docker/entrypoint.d/10-prepare-storage.sh | 19 +++++ routes/web.php | 4 +- 10 files changed, 156 insertions(+), 109 deletions(-) create mode 100644 .dockerignore delete mode 100644 .gitea/workflows/deploy.yml create mode 100644 Dockerfile create mode 100644 config/admin.php create mode 100644 docker-compose.yml create mode 100644 docker/entrypoint.d/10-prepare-storage.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5e5a9a8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +.git +.idea +.vscode +.env +.env.* +!.env.example +.phpunit.result.cache +.phpunit.cache +node_modules +vendor +public/build +public/hot +public/storage +bootstrap/cache/*.php +storage/logs/* +storage/framework/cache/data/* +storage/framework/sessions/* +storage/framework/views/* +storage/app/private/* +storage/app/public/* +database/*.sqlite +test.html +docker-compose.yml +Dockerfile diff --git a/.env.example b/.env.example index 0f35624..9313afe 100644 --- a/.env.example +++ b/.env.example @@ -64,6 +64,9 @@ AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" +ADMIN_USER=admin +ADMIN_PASS=change-me + FILE_LINKS_ENABLED=false FILE_LINKS_DISK=local FILE_LINKS_DIRECTORY=temporary-links diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index 3f550cb..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Deploy back2 - -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - env: - DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} - DEPLOY_USER: ${{ secrets.DEPLOY_USER }} - DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} - DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }} - DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup SSH key - run: | - set -euo pipefail - mkdir -p ~/.ssh - chmod 700 ~/.ssh - printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -p "${DEPLOY_PORT:-22}" -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts - - - name: Deploy Laravel - run: | - set -euo pipefail - - ssh -p "${DEPLOY_PORT:-22}" "$DEPLOY_USER@$DEPLOY_HOST" "DEPLOY_PATH='$DEPLOY_PATH' bash -s" <<'EOF' - set -euo pipefail - - if [ -z "${DEPLOY_PATH:-}" ]; then - echo "DEPLOY_PATH is not set" - exit 1 - fi - - cd "$DEPLOY_PATH" - - if [ ! -d .git ]; then - echo "Expected a git repository at $DEPLOY_PATH" - exit 1 - fi - - git fetch origin main - git checkout main - git reset --hard origin/main - - PHP_BIN="/opt/php84/bin/php" - if [ ! -x "$PHP_BIN" ]; then - PHP_BIN="$(command -v php || true)" - fi - - if [ -z "$PHP_BIN" ] || [ ! -x "$PHP_BIN" ]; then - echo "PHP binary not found" - exit 1 - fi - - COMPOSER_BIN="$(command -v composer || true)" - if [ -z "$COMPOSER_BIN" ]; then - echo "Composer binary not found" - exit 1 - fi - - if [ -f composer.json ]; then - "$PHP_BIN" "$COMPOSER_BIN" install --no-interaction --prefer-dist --optimize-autoloader --no-dev --no-scripts - fi - - if [ -f artisan ]; then - rm -f bootstrap/cache/packages.php bootstrap/cache/services.php bootstrap/cache/config.php bootstrap/cache/events.php bootstrap/cache/routes-*.php - - "$PHP_BIN" artisan down --retry=60 || true - trap '"$PHP_BIN" artisan up || true' EXIT - - "$PHP_BIN" artisan optimize:clear - "$PHP_BIN" artisan package:discover --ansi - "$PHP_BIN" artisan migrate --force - "$PHP_BIN" artisan storage:link || true - "$PHP_BIN" artisan config:cache - "$PHP_BIN" artisan view:cache - "$PHP_BIN" artisan queue:restart || true - fi - EOF diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4875dc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# --- Сборка фронтенда (Vite) --- +FROM node:22-alpine AS assets +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY vite.config.js ./ +COPY resources ./resources +RUN npm run build + +# --- PHP-зависимости --- +FROM serversideup/php:8.4-cli AS vendor +WORKDIR /var/www/html +COPY composer.json composer.lock ./ +RUN composer install --no-dev --no-interaction --prefer-dist --no-scripts --no-autoloader + +# --- Итоговый образ: nginx + php-fpm --- +FROM serversideup/php:8.4-fpm-nginx + +ENV PHP_OPCACHE_ENABLE=1 \ + AUTORUN_ENABLED=true \ + APP_ENV=production \ + APP_DEBUG=false \ + LOG_CHANNEL=stderr \ + DB_CONNECTION=sqlite \ + DB_DATABASE=/var/www/html/storage/database/database.sqlite + +WORKDIR /var/www/html + +COPY --chown=www-data:www-data --from=vendor /var/www/html/vendor ./vendor +COPY --chown=www-data:www-data . . +COPY --chown=www-data:www-data --from=assets /app/public/build ./public/build + +USER root +COPY --chmod=755 docker/entrypoint.d/ /etc/entrypoint.d/ +USER www-data + +RUN composer dump-autoload --optimize --no-dev + +EXPOSE 8080 diff --git a/README.md b/README.md index a137dcb..aca7399 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,58 @@ - ## Repo for site https://back2.iro.kosipov.ru -### Autodeploy (Gitea Actions) +### Deploy (Dokploy) -Workflow file: `.gitea/workflows/deploy.yml` +Проект собирается в Docker-образ на базе `serversideup/php:8.4-fpm-nginx` (nginx + php-fpm, порт `8080`). -Deploy starts automatically on every push to `main`. +Файлы: -Required repository secrets in Gitea: +- `Dockerfile` — сборка ассетов Vite, установка composer-зависимостей, итоговый образ +- `docker-compose.yml` — сервис `app` с томом для `storage` +- `docker/entrypoint.d/10-prepare-storage.sh` — создаёт структуру `storage` и файл SQLite при пустом томе -- `DEPLOY_HOST` - host of the production server (example: `back2.iro.kosipov.ru`) -- `DEPLOY_USER` - SSH user for deploy -- `DEPLOY_PATH` - absolute path to Laravel project on server -- `DEPLOY_SSH_KEY` - private SSH key content (for `DEPLOY_USER`) -- `DEPLOY_PORT` - optional, defaults to `22` +При каждом старте контейнера автоматически выполняются `migrate --force`, `storage:link` и `optimize` (`AUTORUN_ENABLED=true`). -What deployment does: +#### Вариант 1: Application (Dockerfile) -- updates project to `origin/main` -- runs `composer install --no-dev --optimize-autoloader` -- enables maintenance mode (`php artisan down`) -- clears stale caches, runs migrations, recreates Laravel caches -- restarts queue workers (`php artisan queue:restart`) -- disables maintenance mode (`php artisan up`) +1. Create Service → Application, источник — этот репозиторий, ветка `main` +2. Build Type: `Dockerfile` +3. Environment: переменные из списка ниже +4. Advanced → Volumes: Volume Mount, имя `iro2-storage`, путь в контейнере `/var/www/html/storage` +5. Domains: `back2.iro.kosipov.ru`, Container Port `8080`, HTTPS + Let's Encrypt -Server prerequisites: +#### Вариант 2: Docker Compose -- `php`, `composer`, `git`, `bash` available in PATH -- deploy user has permissions for project files and artisan commands +1. Create Service → Compose, источник — этот репозиторий, путь `docker-compose.yml` +2. Environment: переменные из списка ниже (Dokploy запишет их в `.env`) +3. Domains: сервис `app`, порт `8080` + +#### Переменные окружения + +Обязательные: + +``` +APP_KEY=base64:... # php artisan key:generate --show +APP_URL=https://back2.iro.kosipov.ru +ADMIN_USER=... +ADMIN_PASS=... +``` + +Уже заданы в образе (можно переопределить): `APP_ENV=production`, `APP_DEBUG=false`, `LOG_CHANNEL=stderr`, +`DB_CONNECTION=sqlite`, `DB_DATABASE=/var/www/html/storage/database/database.sqlite`. + +Загрузка файлов: + +``` +FILE_LINKS_ENABLED=true +FILE_LINKS_UPLOAD_USER_1=... +FILE_LINKS_UPLOAD_PASSWORD_1=... +FILE_LINKS_UPLOAD_USER_2=... +FILE_LINKS_UPLOAD_PASSWORD_2=... +``` + +База SQLite и загруженные файлы хранятся в томе `storage` — не удаляйте его при пересоздании сервиса. +Для переноса существующих данных скопируйте старый `database/database.sqlite` в +`storage/database/database.sqlite` внутри тома. ### File Upload Basic Auth diff --git a/bootstrap/app.php b/bootstrap/app.php index 13cf64a..60de8fb 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -12,6 +12,9 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + // Dokploy (Traefik) терминирует TLS и проксирует запросы в контейнер + $middleware->trustProxies(at: '*'); + $middleware->alias([ 'file.upload.basic' => \App\Http\Middleware\EnsureFileUploadBasicAuth::class, ]); diff --git a/config/admin.php b/config/admin.php new file mode 100644 index 0000000..ba18a6c --- /dev/null +++ b/config/admin.php @@ -0,0 +1,6 @@ + env('ADMIN_USER'), + 'password' => env('ADMIN_PASS'), +]; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..df038ff --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + env_file: + - .env + expose: + - "8080" + volumes: + - storage:/var/www/html/storage + +volumes: + storage: diff --git a/docker/entrypoint.d/10-prepare-storage.sh b/docker/entrypoint.d/10-prepare-storage.sh new file mode 100644 index 0000000..8809183 --- /dev/null +++ b/docker/entrypoint.d/10-prepare-storage.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# storage монтируется как volume: при пустом томе создаём нужную структуру и файл SQLite +set -e + +cd /var/www/html + +mkdir -p \ + storage/app/public \ + storage/app/private \ + storage/framework/cache/data \ + storage/framework/sessions \ + storage/framework/testing \ + storage/framework/views \ + storage/logs \ + storage/database + +if [ "${DB_CONNECTION:-sqlite}" = "sqlite" ] && [ -n "${DB_DATABASE:-}" ] && [ ! -f "$DB_DATABASE" ]; then + touch "$DB_DATABASE" +fi diff --git a/routes/web.php b/routes/web.php index 16a7e9f..95e4acd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,8 +26,8 @@ Route::get('/thanks', [PokerController::class, 'thanks'])->name('vote.thanks'); Route::prefix('admin')->group(function () { Route::match(['get', 'post'], '/login', function () { if (isset($_SERVER['PHP_AUTH_USER'])) { - if ($_SERVER['PHP_AUTH_USER'] === env('ADMIN_USER') && - $_SERVER['PHP_AUTH_PW'] === env('ADMIN_PASS')) { + if ($_SERVER['PHP_AUTH_USER'] === config('admin.user') && + $_SERVER['PHP_AUTH_PW'] === config('admin.password')) { session(['admin_logged_in' => true]); return redirect('/admin/sessions'); }