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 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 15:39:41 +03:00
parent c5580833f6
commit e639968a46
10 changed files with 156 additions and 109 deletions

24
.dockerignore Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

39
Dockerfile Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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,
]);

6
config/admin.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
return [
'user' => env('ADMIN_USER'),
'password' => env('ADMIN_PASS'),
];

15
docker-compose.yml Normal file
View File

@@ -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:

View File

@@ -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

View File

@@ -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');
}