mirror of
https://github.com/skysyaz/timesheet-staging.git
synced 2026-07-14 08:40:51 +00:00
Make container deploy match the native VPS deploy
Pier rebuilds the container on every commit, so the image is now the real runtime. Bring it to parity with scripts/deploy-staging-vps.sh and make bad builds fail loudly instead of silently drifting: - Entrypoint mirrors the VPS runtime steps: migrate --force, config:cache and view:cache (routes are NOT cached because the /up closure route can't be serialized), favicon generation, and storage permission fixes. - Run web + queue:work + schedule:work under supervisor in one container. Previously nothing processed the database queue and no scheduler ran, so jobs and scheduled tasks never executed. - Declare /app/storage/app as a VOLUME so a Pier persistent volume keeps uploaded attachments across rebuilds (they were on ephemeral disk before). - Add a HEALTHCHECK on the public /login route so Pier can detect a boot that never becomes healthy (/up is IP-restricted and unsuitable from loopback). - composer install now uses --optimize-autoloader, matching the VPS deploy. - Bake trustProxies into bootstrap/app.php and drop the fragile sed injection that silently no-opped if the file was reformatted. - Add .dockerignore so local .env/vendor/node_modules are never baked into image layers and builds stay small. - Set PHP_CLI_SERVER_WORKERS so artisan serve handles concurrent requests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qMh7msL8qSXMA9nSLTY38
This commit is contained in:
parent
f3952b8d55
commit
ef083fd478
18
.dockerignore
Normal file
18
.dockerignore
Normal file
@ -0,0 +1,18 @@
|
||||
# Keep the build context small and avoid baking host-only / secret files
|
||||
# into image layers. composer install and npm ci regenerate vendor/ and
|
||||
# node_modules/ inside the build, so the local copies must never be shipped.
|
||||
.git
|
||||
.gitignore
|
||||
.github
|
||||
node_modules
|
||||
vendor
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
storage/logs/*
|
||||
storage/framework/cache/data/*
|
||||
storage/framework/sessions/*
|
||||
storage/framework/views/*
|
||||
.phpunit.result.cache
|
||||
tests
|
||||
*.md
|
||||
28
Dockerfile
28
Dockerfile
@ -1,7 +1,7 @@
|
||||
FROM node:22-alpine AS node
|
||||
FROM php:8.4-fpm-alpine
|
||||
|
||||
RUN apk add --no-cache icu-libs libzip libpng libjpeg-turbo freetype libxml2 oniguruma curl libpq
|
||||
RUN apk add --no-cache icu-libs libzip libpng libjpeg-turbo freetype libxml2 oniguruma curl libpq supervisor
|
||||
|
||||
RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev libzip-dev libpng-dev libjpeg-turbo-dev freetype-dev libxml2-dev oniguruma-dev postgresql-dev \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
@ -15,19 +15,33 @@ COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||
COPY --from=node /usr/local/bin/node /usr/local/bin/node
|
||||
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm
|
||||
|
||||
# Let `php artisan serve` handle concurrent requests instead of one at a time.
|
||||
ENV PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
RUN sed -i '/->withMiddleware(function (Middleware $middleware): void {/a\ $middleware->trustProxies(at: "*");' bootstrap/app.php \
|
||||
&& composer install --no-dev --no-interaction --no-progress \
|
||||
RUN composer install --no-dev --optimize-autoloader --no-interaction --no-progress \
|
||||
&& npm ci --no-audit --no-fund \
|
||||
&& npm run build \
|
||||
&& rm -rf node_modules \
|
||||
&& chmod +x docker/entrypoint.sh \
|
||||
&& mkdir -p storage/logs storage/framework/cache storage/framework/sessions storage/framework/views storage/app/private/livewire-tmp storage/app/public bootstrap/cache \
|
||||
&& chmod -R 777 storage bootstrap/cache
|
||||
|
||||
# Uploaded attachments live under storage/app (local disk root: storage/app/private).
|
||||
# Declare it as a volume so a Pier persistent volume can be mounted here and
|
||||
# files survive container rebuilds.
|
||||
VOLUME ["/app/storage/app"]
|
||||
|
||||
EXPOSE 8080
|
||||
# Apply any pending database migrations before serving so container-based
|
||||
# deploys stay in sync with the codebase (the VPS deploy script does this too).
|
||||
# Fail fast if migrations can't be applied rather than serving a stale schema.
|
||||
CMD ["sh", "-c", "php artisan migrate --force && php artisan serve --host=0.0.0.0 --port=8080"]
|
||||
|
||||
# Liveness probe. /login is public (200); /up is IP-restricted and would 404
|
||||
# from the loopback interface, so it is not suitable as a container healthcheck.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
||||
CMD curl -fsS http://127.0.0.1:8080/login >/dev/null || exit 1
|
||||
|
||||
# entrypoint runs migrations + cache warming, then execs supervisor, which
|
||||
# supervises the web server, queue worker, and scheduler in one container.
|
||||
ENTRYPOINT ["/app/docker/entrypoint.sh"]
|
||||
CMD ["supervisord", "-c", "/app/docker/supervisord.conf"]
|
||||
|
||||
@ -29,6 +29,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
},
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
$middleware->trustProxies(at: '*');
|
||||
$middleware->redirectGuestsTo('/login');
|
||||
$middleware->append(SecurityHeaders::class);
|
||||
$middleware->web(append: [
|
||||
|
||||
37
docker/entrypoint.sh
Normal file
37
docker/entrypoint.sh
Normal file
@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
# Container entrypoint: mirrors the runtime steps of scripts/deploy-staging-vps.sh
|
||||
# so the containerized app behaves the same as the native VPS deploy.
|
||||
# Runs once per container start, before supervisor launches the processes.
|
||||
set -e
|
||||
|
||||
echo "[entrypoint] Clearing stale caches..."
|
||||
php artisan optimize:clear || true
|
||||
|
||||
echo "[entrypoint] Running database migrations..."
|
||||
php artisan migrate --force
|
||||
|
||||
# Cache config and views (same as the VPS deploy). Routes are intentionally
|
||||
# NOT cached: the /up health route is a closure and cannot be serialized.
|
||||
echo "[entrypoint] Caching config and views..."
|
||||
php artisan config:cache
|
||||
php artisan view:cache
|
||||
|
||||
echo "[entrypoint] Generating favicons..."
|
||||
php scripts/generate-favicons.php || echo "[entrypoint] favicon generation skipped"
|
||||
|
||||
# Recreate storage dirs at runtime: the persistent volume mounted at
|
||||
# storage/app shadows anything created there at build time, so Livewire's
|
||||
# temp-upload dir must be ensured here or "Attach" fails silently.
|
||||
echo "[entrypoint] Ensuring storage directories exist and are writable..."
|
||||
mkdir -p \
|
||||
storage/app/private/livewire-tmp \
|
||||
storage/app/public \
|
||||
storage/framework/cache/data \
|
||||
storage/framework/sessions \
|
||||
storage/framework/views \
|
||||
storage/logs \
|
||||
bootstrap/cache
|
||||
chmod -R 775 storage bootstrap/cache || true
|
||||
|
||||
echo "[entrypoint] Starting supervisor..."
|
||||
exec "$@"
|
||||
47
docker/supervisord.conf
Normal file
47
docker/supervisord.conf
Normal file
@ -0,0 +1,47 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
pidfile=/tmp/supervisord.pid
|
||||
|
||||
; HTTP server. artisan serve is single-threaded per worker; PHP_CLI_SERVER_WORKERS
|
||||
; (set in the Dockerfile) lets it handle concurrent requests.
|
||||
[program:web]
|
||||
command=php artisan serve --host=0.0.0.0 --port=8080
|
||||
directory=/app
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=10
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
priority=10
|
||||
|
||||
; Queue worker (QUEUE_CONNECTION=database). Without this, dispatched jobs
|
||||
; (broadcast emails, heartbeats) would never be processed.
|
||||
[program:queue]
|
||||
command=php artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||
directory=/app
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopwaitsecs=3630
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
priority=20
|
||||
|
||||
; Scheduler. schedule:work is the container-friendly equivalent of a
|
||||
; "* * * * * schedule:run" cron entry; it stays in the foreground.
|
||||
[program:scheduler]
|
||||
command=php artisan schedule:work
|
||||
directory=/app
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
priority=30
|
||||
Loading…
x
Reference in New Issue
Block a user