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:
24
.dockerignore
Normal file
24
.dockerignore
Normal 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
|
||||||
@@ -64,6 +64,9 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
|
|||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
ADMIN_USER=admin
|
||||||
|
ADMIN_PASS=change-me
|
||||||
|
|
||||||
FILE_LINKS_ENABLED=false
|
FILE_LINKS_ENABLED=false
|
||||||
FILE_LINKS_DISK=local
|
FILE_LINKS_DISK=local
|
||||||
FILE_LINKS_DIRECTORY=temporary-links
|
FILE_LINKS_DIRECTORY=temporary-links
|
||||||
|
|||||||
@@ -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
39
Dockerfile
Normal 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
|
||||||
65
README.md
65
README.md
@@ -1,33 +1,58 @@
|
|||||||
|
|
||||||
## Repo for site https://back2.iro.kosipov.ru
|
## 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`)
|
При каждом старте контейнера автоматически выполняются `migrate --force`, `storage:link` и `optimize` (`AUTORUN_ENABLED=true`).
|
||||||
- `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`
|
|
||||||
|
|
||||||
What deployment does:
|
#### Вариант 1: Application (Dockerfile)
|
||||||
|
|
||||||
- updates project to `origin/main`
|
1. Create Service → Application, источник — этот репозиторий, ветка `main`
|
||||||
- runs `composer install --no-dev --optimize-autoloader`
|
2. Build Type: `Dockerfile`
|
||||||
- enables maintenance mode (`php artisan down`)
|
3. Environment: переменные из списка ниже
|
||||||
- clears stale caches, runs migrations, recreates Laravel caches
|
4. Advanced → Volumes: Volume Mount, имя `iro2-storage`, путь в контейнере `/var/www/html/storage`
|
||||||
- restarts queue workers (`php artisan queue:restart`)
|
5. Domains: `back2.iro.kosipov.ru`, Container Port `8080`, HTTPS + Let's Encrypt
|
||||||
- disables maintenance mode (`php artisan up`)
|
|
||||||
|
|
||||||
Server prerequisites:
|
#### Вариант 2: Docker Compose
|
||||||
|
|
||||||
- `php`, `composer`, `git`, `bash` available in PATH
|
1. Create Service → Compose, источник — этот репозиторий, путь `docker-compose.yml`
|
||||||
- deploy user has permissions for project files and artisan commands
|
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
|
### File Upload Basic Auth
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
|
// Dokploy (Traefik) терминирует TLS и проксирует запросы в контейнер
|
||||||
|
$middleware->trustProxies(at: '*');
|
||||||
|
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'file.upload.basic' => \App\Http\Middleware\EnsureFileUploadBasicAuth::class,
|
'file.upload.basic' => \App\Http\Middleware\EnsureFileUploadBasicAuth::class,
|
||||||
]);
|
]);
|
||||||
|
|||||||
6
config/admin.php
Normal file
6
config/admin.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'user' => env('ADMIN_USER'),
|
||||||
|
'password' => env('ADMIN_PASS'),
|
||||||
|
];
|
||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal 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:
|
||||||
19
docker/entrypoint.d/10-prepare-storage.sh
Normal file
19
docker/entrypoint.d/10-prepare-storage.sh
Normal 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
|
||||||
@@ -26,8 +26,8 @@ Route::get('/thanks', [PokerController::class, 'thanks'])->name('vote.thanks');
|
|||||||
Route::prefix('admin')->group(function () {
|
Route::prefix('admin')->group(function () {
|
||||||
Route::match(['get', 'post'], '/login', function () {
|
Route::match(['get', 'post'], '/login', function () {
|
||||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||||
if ($_SERVER['PHP_AUTH_USER'] === env('ADMIN_USER') &&
|
if ($_SERVER['PHP_AUTH_USER'] === config('admin.user') &&
|
||||||
$_SERVER['PHP_AUTH_PW'] === env('ADMIN_PASS')) {
|
$_SERVER['PHP_AUTH_PW'] === config('admin.password')) {
|
||||||
session(['admin_logged_in' => true]);
|
session(['admin_logged_in' => true]);
|
||||||
return redirect('/admin/sessions');
|
return redirect('/admin/sessions');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user