Private
Public Access
1
0

chg: pkg: make compatible the whole project with bare metal AND with docker #4

This commit is contained in:
2026-04-13 11:56:50 +02:00
parent e9c6795eb7
commit 4239177563
10 changed files with 240 additions and 9 deletions

12
.dockerignore Normal file
View File

@@ -0,0 +1,12 @@
.git
.gitignore
node_modules
var/cache
var/log
var/sessions
public/build
*.md
docker-compose*.yml
compose.override.yaml
.env.local
.env.*.local

View File

@@ -16,7 +16,8 @@ APP_SECRET=c1c278747d952ea66326352b72bb8ec6
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
###< doctrine/doctrine-bundle ### ###< doctrine/doctrine-bundle ###
###> symfony/mailer ### ###> symfony/mailer ###
# MAILER_DSN=smtp://localhost MAILER_DSN=smtp://localhost:1025
MAIL_DOMAIN=localhost
###< symfony/mailer ### ###< symfony/mailer ###
###> symfony/mercure-bundle ### ###> symfony/mercure-bundle ###

25
Caddyfile Normal file
View File

@@ -0,0 +1,25 @@
{
{$CADDY_GLOBAL_OPTIONS}
frankenphp {
{$FRANKENPHP_CONFIG}
}
}
{$SERVER_NAME:localhost} {
log
root * /app/public
encode zstd br gzip
mercure {
transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
publisher_jwt {$MERCURE_JWT_SECRET} HS256
subscriber_jwt {$MERCURE_JWT_SECRET} HS256
anonymous
subscriptions
}
php_server
}

52
Dockerfile Normal file
View File

@@ -0,0 +1,52 @@
FROM node:22-alpine AS assets
WORKDIR /app
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY vite.config.js ./
COPY assets/ assets/
COPY public/ public/
RUN npm run build
FROM dunglas/frankenphp:latest
RUN install-php-extensions \
pdo_pgsql \
gd \
intl \
zip \
opcache \
apcu \
sodium
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \
> "$PHP_INI_DIR/conf.d/opcache.ini"
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
ENV APP_ENV=prod
WORKDIR /app
COPY . .
RUN composer install \
--no-dev \
--no-interaction \
--no-scripts \
--optimize-autoloader
COPY --from=assets /app/public/build ./public/build
RUN mkdir -p var/cache var/log var/sessions && \
chown -R www-data:www-data var/
COPY Caddyfile /etc/caddy/Caddyfile
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

48
Makefile Normal file
View File

@@ -0,0 +1,48 @@
.PHONY: help start start-build stop build down ps logs prune-everything db-reset
.DEFAULT_GOAL := help
help:
@echo "Available commands:"
@echo " make start - Start services without building (uses existing images)"
@echo " make start-build - Start services and build images if needed"
@echo " make stop - Stop running services"
@echo " make build - Build Docker images only"
@echo " make down - Stop and remove containers/networks"
@echo " make prune-everything - Prune volumes, networks and images (DANGEROUS!)"
@echo " make db-reset - Reset the database (drop, create, migrate) (DANGEROUS!)"
start:
docker compose up -d
start-build:
docker compose up -d --build
stop:
docker compose stop
build:
docker compose build
down:
docker compose down
prune-everything:
@echo "WARNING: This will remove ALL containers, volumes, networks and images!"
@read -p "Type 'yes' to confirm: " confirm; \
if [ "$$confirm" != "yes" ]; then \
echo "Aborted."; \
exit 1; \
fi
docker compose down -v --rmi all --remove-orphans
db-reset:
@echo "WARNING: This will DROP and RECREATE the database!"
@read -p "Type 'yes' to confirm: " confirm; \
if [ "$$confirm" != "yes" ]; then \
echo "Aborted."; \
exit 1; \
fi
bin/console doctrine:database:drop --force --if-exists --no-interaction
bin/console doctrine:database:create --if-not-exists --no-interaction
bin/console doctrine:migrations:migrate --no-interaction

69
compose.yaml Normal file
View File

@@ -0,0 +1,69 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "10080:80"
environment:
SERVER_NAME: ${SERVER_NAME:-:80}
APP_ENV: prod
APP_SECRET: ${APP_SECRET}
DATABASE_URL: >-
postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8
POSTGRES_URL: db
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
MERCURE_URL: http://localhost/.well-known/mercure
MERCURE_PUBLIC_URL: https://${PUBLIC_HOSTNAME:-localhost}/.well-known/mercure
MERCURE_JWT_SECRET: ${MERCURE_JWT_SECRET}
MERCURE_JWT_TOKEN: ${MERCURE_JWT_TOKEN}
MERCURE_SUBSCRIBER_JWT: ${MERCURE_SUBSCRIBER_JWT}
MAILER_DSN: smtp://mail:25?verify_peer=0
RECAPTCHA_SITE_KEY: ${RECAPTCHA_SITE_KEY}
RECAPTCHA_SECRET_KEY: ${RECAPTCHA_SECRET_KEY}
WEBAUTHN_RP_ID: ${WEBAUTHN_RP_ID:-localhost}
WEBAUTHN_ORIGIN: ${WEBAUTHN_ORIGIN:-https://localhost}
volumes:
- app_var:/app/var
- caddy_data:/data
- caddy_config:/config
depends_on:
db:
condition: service_healthy
mail:
condition: service_started
mail:
image: boky/postfix:latest
restart: unless-stopped
environment:
ALLOWED_SENDER_DOMAINS: ${MAIL_DOMAIN:-localhost}
# Optional: set a relay host if you need to forward to an external SMTP
# RELAYHOST: "[smtp.example.com]:587"
volumes:
- postfix_spool:/var/spool/postfix
db:
image: postgres:${POSTGRES_VERSION:-latest}-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
ports:
- "15432:5432"
volumes:
app_var:
postgres_data:
caddy_data:
caddy_config:
postfix_spool:

View File

@@ -7,14 +7,7 @@ parameters:
doctrine: doctrine:
dbal: dbal:
# configure these for your database server driver: 'pdo_pgsql'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%' url: '%env(resolve:DATABASE_URL)%'
orm: orm:

18
docker/entrypoint.sh Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
set -e
echo "[entrypoint] Waiting for database..."
until php -r "new PDO('pgsql:host=db;port=5432;dbname=${POSTGRES_DB}', '${POSTGRES_USER}', '${POSTGRES_PASSWORD}');" 2>/dev/null; do
echo "[entrypoint] Database not ready, retrying in 2s..."
sleep 2
done
echo "[entrypoint] Database is ready."
echo "[entrypoint] Warming up Symfony cache..."
php bin/console cache:warmup
echo "[entrypoint] Running database migrations..."
php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
echo "[entrypoint] Starting FrankenPHP..."
exec frankenphp run --config /etc/caddy/Caddyfile "$@"

View File

@@ -42,6 +42,11 @@ class RecaptchaType extends AbstractType
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event): void { $builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event): void {
$request = $this->requestStack->getCurrentRequest(); $request = $this->requestStack->getCurrentRequest();
$token = $request?->request->getString('g-recaptcha-response') ?? ''; $token = $request?->request->getString('g-recaptcha-response') ?? '';
// For forms that set the token directly on the field (e.g. registration_form[recaptcha])
// rather than via a standalone g-recaptcha-response input, fall back to the submitted value.
if ($token === '') {
$token = (string) ($event->getData() ?? '');
}
$event->setData($token); $event->setData($token);
}); });
} }

View File

@@ -58,6 +58,14 @@ readonly class RecaptchaService
->request('POST', self::SITEVERIFY_URL, ['body' => $body]) ->request('POST', self::SITEVERIFY_URL, ['body' => $body])
->toArray(); ->toArray();
$this->logger->info('reCAPTCHA verify response', [
'success' => $data['success'] ?? null,
'score' => $data['score'] ?? null,
'hostname' => $data['hostname'] ?? null,
'error-codes' => $data['error-codes'] ?? [],
'token_length' => strlen($token),
]);
return ($data['success'] ?? false) === true return ($data['success'] ?? false) === true
&& ($data['score'] ?? 0.0) >= self::SCORE_THRESHOLD; && ($data['score'] ?? 0.0) >= self::SCORE_THRESHOLD;
} catch (\Throwable $e) { } catch (\Throwable $e) {