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}"
|
||||
|
||||
ADMIN_USER=admin
|
||||
ADMIN_PASS=change-me
|
||||
|
||||
FILE_LINKS_ENABLED=false
|
||||
FILE_LINKS_DISK=local
|
||||
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
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -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
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::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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user