1 - Add production Dockerfile

This commit is contained in:
myrmidex 2026-04-23 19:55:57 +02:00
parent fa85decccb
commit f0a8bdc1de
3 changed files with 179 additions and 3 deletions

49
.dockerignore Normal file
View file

@ -0,0 +1,49 @@
# Version control
.git
.gitignore
.gitattributes
# Dev environment
shell.nix
Dockerfile.dev
docker/
# Tests (not needed in prod image)
tests/
phpunit.xml
.phpunit.result.cache
phpstan.neon
# Dependencies (rebuilt during image build)
node_modules/
vendor/
# Build artifacts (frontend stage produces these)
public/build/
public/hot
# Editor / OS
.editorconfig
.idea/
.vscode/
.DS_Store
*.swp
*.swo
# Env / secrets
.env
.env.*
!.env.example
# Logs and runtime caches
storage/logs/*.log
storage/framework/cache/data/
storage/framework/sessions/
storage/framework/views/
# CI
.forgejo/
# Docs / project meta
README.md
LICENSE

View file

@ -5,8 +5,7 @@ on:
branches: [main]
tags: ['v*']
paths:
- 'Dockerfile'
- 'docker/**'
- 'docker/prod/Dockerfile'
- 'app/**'
- 'bootstrap/**'
- 'config/**'
@ -51,6 +50,6 @@ jobs:
uses: https://data.forgejo.org/docker/build-push-action@v5
with:
context: .
file: Dockerfile
file: docker/prod/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}

128
docker/prod/Dockerfile Normal file
View file

@ -0,0 +1,128 @@
# syntax=docker/dockerfile:1
# ============================================================
# Stage 1: Build frontend assets
# ============================================================
FROM node:20-alpine AS frontend
WORKDIR /app
COPY package.json package-lock.json vite.config.js ./
COPY resources/ resources/
RUN npm ci --no-audit --no-fund
RUN npm run build
# ============================================================
# Stage 2: Runtime (FrankenPHP)
# ============================================================
FROM dunglas/frankenphp:1.1-php8.3-alpine AS runtime
RUN apk add --no-cache \
git \
postgresql-client \
curl
RUN install-php-extensions \
pdo_pgsql \
redis \
opcache \
zip \
gd \
intl
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
ENV APP_ENV=production \
APP_DEBUG=false \
LOG_CHANNEL=stack \
LOG_LEVEL=warning \
DB_CONNECTION=pgsql \
DB_HOST=db \
DB_PORT=5432 \
REDIS_HOST=redis \
REDIS_PORT=6379 \
CACHE_STORE=redis \
QUEUE_CONNECTION=redis \
SESSION_DRIVER=redis \
BROADCAST_CONNECTION=log \
MAIL_MAILER=log
# Copy only the files composer needs before install, so the composer layer stays
# cached when application source changes. packages/ is required because composer.json
# declares it as a path repository.
COPY composer.json composer.lock ./
COPY packages/ packages/
# Skip post-autoload scripts (package:discover) during build — they need a runtime
# Laravel boot which fails without proper env. Discovery happens at runtime via
# start-prod.sh. --classmap-authoritative implies --optimize-autoloader.
RUN composer install --no-dev --no-interaction --prefer-dist --classmap-authoritative --no-scripts
COPY . .
COPY --from=frontend /app/public/build /app/public/build
RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache
RUN cat > /etc/caddy/Caddyfile <<'EOF'
{
frankenphp
order php_server before file_server
}
:8000 {
root * /app/public
php_server {
index index.php
}
encode gzip zstd
file_server
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -fsS http://localhost:8000/up || exit 1
RUN cat > /start-prod.sh <<'EOF'
#!/bin/sh
set -e
echo "Waiting for PostgreSQL at ${DB_HOST}:${DB_PORT}..."
for i in $(seq 1 60); do
if pg_isready -h "${DB_HOST}" -p "${DB_PORT}" -q; then
echo "PostgreSQL is ready."
break
fi
if [ "$i" = "60" ]; then
echo "Timed out waiting for PostgreSQL after 60s." >&2
exit 1
fi
sleep 1
done
php artisan package:discover --ansi
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
exec frankenphp run --config /etc/caddy/Caddyfile
EOF
RUN chmod +x /start-prod.sh
CMD ["/start-prod.sh"]