From 0ddf5c874dc1853d8f464bfde7d8361fa9d9469e Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Sat, 18 Jan 2025 20:24:02 -0800 Subject: [PATCH 1/8] Rootless Dockerfile/Optimized build Add unneeded files to .dockerignore Split Dockerfile into more stages to allow Composer/Yarn to run concurrently Don't log supervisord to a file, as file logging in a Docker container makes no sense Redirect process output to container output for log processors Run all processes as non-root Minimize files with write permission for non-root user Move docker folder out of .github, as it has nothing to do with GitHub --- .dockerignore | 23 ++++- Dockerfile | 95 +++++++++++++-------- compose.yml | 1 + {.github/docker => docker}/README.md | 0 {.github/docker => docker}/entrypoint.sh | 4 - {.github/docker => docker}/supervisord.conf | 24 +++--- 6 files changed, 96 insertions(+), 51 deletions(-) rename {.github/docker => docker}/README.md (100%) rename {.github/docker => docker}/entrypoint.sh (94%) rename {.github/docker => docker}/supervisord.conf (63%) diff --git a/.dockerignore b/.dockerignore index 0af94525e7..a057373363 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,27 @@ +**.DS_Store +.devcontainer +.dockerignore +.editorconfig .git -node_modules -vendor +.github +**.gitignore +.php-cs-fixer.dist.php +.prettierrc.json +.vscode +Dockerfile +bounties.md +contributing.md +contributor_license_agreement.md database/database.sqlite +docker/README.md +node_modules +phpstan.neon +phpunit.xml +readme.md storage/debugbar/*.json -storage/logs/*.log storage/framework/cache/data/* storage/framework/sessions/* storage/framework/testing storage/framework/views/*.php +storage/logs/*.log +vendor diff --git a/Dockerfile b/Dockerfile index 2bc0f23e3d..0d8620e4e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,69 +1,94 @@ +# syntax=docker.io/docker/dockerfile:1.7-labs # Pelican Production Dockerfile # ================================ -# Stage 1: Build PHP Dependencies +# Stage 1: Build PHP Base Image # ================================ -FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine AS composer +FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine AS base + +ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ + +RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql + +# ================================ +# Stage 2-1: Composer Install +# ================================ +FROM --platform=$TARGETOS/$TARGETARCH base AS composer WORKDIR /build -COPY . ./ COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer -# Install required libraries and PHP extensions -RUN apk update && apk add --no-cache \ - libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \ - zip unzip curl \ - && docker-php-ext-install bcmath gd intl zip opcache pcntl posix pdo_mysql +# Copy bare minimum to install Composer dependencies +COPY composer.json composer.lock ./ -RUN composer install --no-dev --optimize-autoloader +RUN composer install --no-dev --no-interaction --no-autoloader --no-scripts # ================================ -# Stage 2: Build Frontend Assets +# Stage 2-2: Yarn Install # ================================ FROM --platform=$TARGETOS/$TARGETARCH node:20-alpine AS yarn WORKDIR /build -COPY --from=composer /build . +# Copy bare minimum to install Yarn dependencies +COPY package.json yarn.lock ./ RUN yarn config set network-timeout 300000 \ - && yarn install --frozen-lockfile \ - && yarn run build + && yarn install --frozen-lockfile # ================================ -# Stage 3: Build Final Application Image +# Stage 3-1: Composer Optimize # ================================ -FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine +FROM --platform=$TARGETOS/$TARGETARCH composer AS composerbuild -WORKDIR /var/www/html +# Copy full code to optimize autoload +COPY --exclude=Caddyfile --exclude=docker/ . ./ -COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer +RUN composer dump-autoload --optimize + +# ================================ +# Stage 3-2: Build Frontend Assets +# ================================ +FROM --platform=$TARGETOS/$TARGETARCH yarn AS yarnbuild + +WORKDIR /build + +# Copy full code +COPY --exclude=Caddyfile --exclude=docker/ . ./ +COPY --from=composer /build . + +RUN yarn run build + +# ================================ +# Stage 4: Build Final Application Image +# ================================ +FROM --platform=$TARGETOS/$TARGETARCH base + +WORKDIR /var/www/html # Install additional required libraries RUN apk update && apk add --no-cache \ - libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \ - zip unzip curl caddy ca-certificates supervisor - -# Copy PHP extensions and configuration from Composer stage -COPY --from=composer /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/ -COPY --from=composer /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/ + caddy ca-certificates supervisor supercronic COPY Caddyfile /etc/caddy/Caddyfile -COPY --from=yarn /build . - -RUN touch .env +COPY --from=composerbuild /build . +COPY --from=yarnbuild /build/public ./public # Set permissions for Laravel directories -RUN chmod -R 755 storage bootstrap/cache \ - && chown -R www-data:www-data ./ - -# Add Laravel scheduler to crontab -RUN echo "* * * * * php /var/www/html/artisan schedule:run >> /dev/null 2>&1" | crontab -u www-data - +RUN mkdir -p /pelican-data /var/run/supervisord /etc/supercronic \ + && chmod -R 755 /pelican-data storage bootstrap/cache /var/run/supervisord \ + && chown -R www-data:www-data /pelican-data storage bootstrap/cache /var/run/supervisord \ + # Only database folder permissions are needed to link to sqlite database, no deeper + && chmod 755 database \ + && chown www-data:www-data database \ + # Add Laravel scheduler to crontab + && echo "* * * * * php /var/www/html/artisan schedule:run" > /etc/supercronic/crontab # Configure Supervisor -RUN cp .github/docker/supervisord.conf /etc/supervisord.conf && \ - mkdir /var/log/supervisord/ +COPY docker/supervisord.conf /etc/supervisord.conf + +COPY docker/entrypoint.sh ./docker/entrypoint.sh HEALTHCHECK --interval=5m --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost/up || exit 1 @@ -72,5 +97,7 @@ EXPOSE 80 443 VOLUME /pelican-data -ENTRYPOINT [ "/bin/ash", ".github/docker/entrypoint.sh" ] +USER www-data + +ENTRYPOINT [ "/bin/ash", "docker/entrypoint.sh" ] CMD [ "supervisord", "-n", "-c", "/etc/supervisord.conf" ] diff --git a/compose.yml b/compose.yml index 6dab7145e3..058c5e375e 100644 --- a/compose.yml +++ b/compose.yml @@ -28,6 +28,7 @@ x-common: services: panel: image: ghcr.io/pelican-dev/panel:latest + build: . restart: always networks: - default diff --git a/.github/docker/README.md b/docker/README.md similarity index 100% rename from .github/docker/README.md rename to docker/README.md diff --git a/.github/docker/entrypoint.sh b/docker/entrypoint.sh similarity index 94% rename from .github/docker/entrypoint.sh rename to docker/entrypoint.sh index 653c3e4bad..af9060ec60 100644 --- a/.github/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -45,10 +45,6 @@ php artisan migrate --force echo -e "Optimizing Filament" php artisan filament:optimize -## start cronjobs for the queue -echo -e "Starting cron jobs." -crond -L /var/log/crond -l 5 - export SUPERVISORD_CADDY=false ## disable caddy if SKIP_CADDY is set diff --git a/.github/docker/supervisord.conf b/docker/supervisord.conf similarity index 63% rename from .github/docker/supervisord.conf rename to docker/supervisord.conf index 1094f0d23d..9359eb1b84 100644 --- a/.github/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -4,16 +4,14 @@ username=dummy password=dummy [supervisord] -logfile=/var/log/supervisord/supervisord.log ; supervisord log file -logfile_maxbytes=50MB ; maximum size of logfile before rotation -logfile_backups=2 ; number of backed up logfiles +logfile=/dev/null +logfile_maxbytes=0 loglevel=error ; info, debug, warn, trace -pidfile=/var/run/supervisord.pid ; pidfile location -nodaemon=false ; run supervisord as a daemon +pidfile=/var/run/supervisord/supervisord.pid ; pidfile location +nodaemon=true ; run supervisord as a daemon minfds=1024 ; number of startup file descriptors minprocs=200 ; number of process descriptors -user=root ; default user -childlogdir=/var/log/supervisord/ ; where child log files will live +user=www-data ; default user [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface @@ -30,7 +28,6 @@ autorestart=true [program:queue-worker] command=/usr/local/bin/php /var/www/html/artisan queue:work --tries=3 -user=www-data autostart=true autorestart=true @@ -39,5 +36,12 @@ command=caddy run --config /etc/caddy/Caddyfile --adapter caddyfile autostart=%(ENV_SUPERVISORD_CADDY)s autorestart=%(ENV_SUPERVISORD_CADDY)s priority=10 -stdout_events_enabled=true -stderr_events_enabled=true +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +redirect_stderr=true + +[program:supercronic] +command=supercronic /etc/supercronic/crontab +autostart=true +autorestart=true +redirect_stderr=true From 74f65c91e9edf6604464a2a8a95b7c654acb6f4d Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Sat, 18 Jan 2025 21:27:42 -0800 Subject: [PATCH 2/8] Remove install-php-extensions utility after use and name final stage --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0d8620e4e7..534473e9b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,8 @@ ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/relea RUN install-php-extensions bcmath gd intl zip opcache pcntl posix pdo_mysql +RUN rm /usr/local/bin/install-php-extensions + # ================================ # Stage 2-1: Composer Install # ================================ @@ -63,7 +65,7 @@ RUN yarn run build # ================================ # Stage 4: Build Final Application Image # ================================ -FROM --platform=$TARGETOS/$TARGETARCH base +FROM --platform=$TARGETOS/$TARGETARCH base AS final WORKDIR /var/www/html From 20e46b5bd85653142dec2a8acd3eaac018aab026 Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Sat, 18 Jan 2025 21:29:14 -0800 Subject: [PATCH 3/8] Test arm64 runner --- .github/workflows/docker-publish.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 1d540975d0..cd59bb7127 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,8 +2,6 @@ name: Docker on: push: - branches: - - main release: types: - published From ed1473f15b2d251e3ca77444e50ee6db94394c3a Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Sat, 18 Jan 2025 23:07:43 -0800 Subject: [PATCH 4/8] Allow Docker workflow caching multi-arch separately --- .github/workflows/docker-publish.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index cd59bb7127..e17c6cf715 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -69,6 +69,8 @@ jobs: VERSION=${{ steps.build_info.outputs.version_tag }} labels: ${{ steps.docker_meta.outputs.labels }} tags: ${{ steps.docker_meta.outputs.tags }} + cache-from: type=gha,scope=tagged${{ matrix.os }} + cache-to: type=gha,scope=tagged${{ matrix.os }},mode=max - name: Build and Push (main) uses: docker/build-push-action@v6 @@ -82,5 +84,5 @@ jobs: VERSION=dev-${{ steps.build_info.outputs.short_sha }} labels: ${{ steps.docker_meta.outputs.labels }} tags: ${{ steps.docker_meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ matrix.os }} + cache-to: type=gha,scope=${{ matrix.os }},mode=max From fd3cce280e017f66a290322cb0fff6d774062e05 Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Sun, 19 Jan 2025 00:03:18 -0800 Subject: [PATCH 5/8] Fix Docker publish workflow branches --- .github/workflows/docker-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index e17c6cf715..e49cd5e4ce 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,6 +2,8 @@ name: Docker on: push: + branches: + - main release: types: - published From 30f765e95f90137bdb7746901943dadc3289c1c0 Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Sun, 19 Jan 2025 10:23:57 +0000 Subject: [PATCH 6/8] Move Caddyfile/crontab config into docker directory, remove redundant supervisord user --- .dockerignore | 1 + Dockerfile | 10 +++++----- Caddyfile => docker/Caddyfile | 0 docker/crontab | 1 + docker/entrypoint.sh | 2 -- docker/supervisord.conf | 1 - 6 files changed, 7 insertions(+), 8 deletions(-) rename Caddyfile => docker/Caddyfile (100%) create mode 100644 docker/crontab diff --git a/.dockerignore b/.dockerignore index a057373363..2f9e9ea9b8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ **.DS_Store +.env .devcontainer .dockerignore .editorconfig diff --git a/Dockerfile b/Dockerfile index 534473e9b2..132991a641 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,22 +73,22 @@ WORKDIR /var/www/html RUN apk update && apk add --no-cache \ caddy ca-certificates supervisor supercronic -COPY Caddyfile /etc/caddy/Caddyfile COPY --from=composerbuild /build . COPY --from=yarnbuild /build/public ./public -# Set permissions for Laravel directories +# Set permissions for Laravel/Caddy/Supervisord directories RUN mkdir -p /pelican-data /var/run/supervisord /etc/supercronic \ && chmod -R 755 /pelican-data storage bootstrap/cache /var/run/supervisord \ && chown -R www-data:www-data /pelican-data storage bootstrap/cache /var/run/supervisord \ # Only database folder permissions are needed to link to sqlite database, no deeper && chmod 755 database \ - && chown www-data:www-data database \ - # Add Laravel scheduler to crontab - && echo "* * * * * php /var/www/html/artisan schedule:run" > /etc/supercronic/crontab + && chown www-data:www-data database # Configure Supervisor COPY docker/supervisord.conf /etc/supervisord.conf +COPY docker/Caddyfile /etc/caddy/Caddyfile +# Add Laravel scheduler to crontab +COPY docker/crontab /etc/supercronic/crontab COPY docker/entrypoint.sh ./docker/entrypoint.sh diff --git a/Caddyfile b/docker/Caddyfile similarity index 100% rename from Caddyfile rename to docker/Caddyfile diff --git a/docker/crontab b/docker/crontab new file mode 100644 index 0000000000..f67a6bf177 --- /dev/null +++ b/docker/crontab @@ -0,0 +1 @@ +* * * * * php /var/www/html/artisan schedule:run diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index af9060ec60..c6f9125dc2 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,7 +1,5 @@ #!/bin/ash -e -#mkdir -p /var/log/supervisord/ /var/log/php8/ \ - ## check for .env file and generate app keys if missing if [ -f /pelican-data/.env ]; then echo "external vars exist." diff --git a/docker/supervisord.conf b/docker/supervisord.conf index 9359eb1b84..085086b3f8 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -11,7 +11,6 @@ pidfile=/var/run/supervisord/supervisord.pid ; pidfile location nodaemon=true ; run supervisord as a daemon minfds=1024 ; number of startup file descriptors minprocs=200 ; number of process descriptors -user=www-data ; default user [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface From 6de2a72bfdc6b4bf3573377a4da61705df143c1c Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Mon, 20 Jan 2025 08:41:46 +0000 Subject: [PATCH 7/8] Further restrict permissions --- .dockerignore | 1 + Dockerfile | 27 +++++++++++++++++---------- docker/entrypoint.sh | 12 +++--------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.dockerignore b/.dockerignore index 2f9e9ea9b8..7c3825a172 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,7 @@ .vscode Dockerfile bounties.md +compose.yml contributing.md contributor_license_agreement.md database/database.sqlite diff --git a/Dockerfile b/Dockerfile index 132991a641..b12576776e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,16 +73,23 @@ WORKDIR /var/www/html RUN apk update && apk add --no-cache \ caddy ca-certificates supervisor supercronic -COPY --from=composerbuild /build . -COPY --from=yarnbuild /build/public ./public - -# Set permissions for Laravel/Caddy/Supervisord directories -RUN mkdir -p /pelican-data /var/run/supervisord /etc/supercronic \ - && chmod -R 755 /pelican-data storage bootstrap/cache /var/run/supervisord \ - && chown -R www-data:www-data /pelican-data storage bootstrap/cache /var/run/supervisord \ - # Only database folder permissions are needed to link to sqlite database, no deeper - && chmod 755 database \ - && chown www-data:www-data database +COPY --chown=root:www-data --chmod=640 --from=composerbuild /build . +COPY --chown=root:www-data --chmod=640 --from=yarnbuild /build/public ./public + +# Set permissions +# First ensure all files are owned by root and restrict www-data to read access +RUN chown root:www-data ./ \ + && chmod 750 ./ \ + # Files should not have execute set, but directories need it + && find ./ -type d -exec chmod 750 {} \; \ + # Symlink to env/database path, as www-data won't be able to write to webroot + && ln -s /pelican-data/.env ./.env \ + && ln -s /pelican-data/database/database.sqlite ./database/database.sqlite \ + # Create necessary directories + && mkdir -p /pelican-data /var/run/supervisord /etc/supercronic \ + # Finally allow www-data write permissions where necessary + && chown -R www-data:www-data /pelican-data ./storage ./bootstrap/cache /var/run/supervisord \ + && chmod -R u+rwX,g+rwX,o-rwx /pelican-data ./storage ./bootstrap/cache /var/run/supervisord # Configure Supervisor COPY docker/supervisord.conf /etc/supervisord.conf diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index c6f9125dc2..6b66d9a85d 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,12 +1,11 @@ #!/bin/ash -e -## check for .env file and generate app keys if missing -if [ -f /pelican-data/.env ]; then +## check for .env file or symlink and generate app keys if missing +if [ -f /var/www/html/.env ]; then echo "external vars exist." - rm -rf /var/www/html/.env else echo "external vars don't exist." - rm -rf /var/www/html/.env + # webroot .env is symlinked to this path touch /pelican-data/.env ## manually generate a key because key generate --force fails @@ -25,9 +24,6 @@ else fi mkdir /pelican-data/database -ln -s /pelican-data/.env /var/www/html/ -chown -h www-data:www-data /var/www/html/.env -ln -s /pelican-data/database/database.sqlite /var/www/html/database/ if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then echo "Generating APP_KEY..." @@ -53,7 +49,5 @@ else export SUPERVISORD_CADDY=true fi -chown -R www-data:www-data /pelican-data/.env /pelican-data/database - echo "Starting Supervisord" exec "$@" From 0336e5533cbacdd3abc39d4fecaa31dbd9b7df6c Mon Sep 17 00:00:00 2001 From: PseudoResonance Date: Wed, 22 Jan 2025 09:39:32 +0000 Subject: [PATCH 8/8] Supervisord logs --- docker/entrypoint.sh | 2 +- docker/supervisord.conf | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 6b66d9a85d..c97577bf30 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -23,7 +23,7 @@ else echo -e "APP_INSTALLED=false" >> /pelican-data/.env fi -mkdir /pelican-data/database +mkdir /pelican-data/database /var/www/html/storage/logs/supervisord 2>/dev/null if ! grep -q "APP_KEY=" .env || grep -q "APP_KEY=$" .env; then echo "Generating APP_KEY..." diff --git a/docker/supervisord.conf b/docker/supervisord.conf index 085086b3f8..83a27a10df 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -4,8 +4,9 @@ username=dummy password=dummy [supervisord] -logfile=/dev/null -logfile_maxbytes=0 +logfile=/var/www/html/storage/logs/supervisord/supervisord.log ; supervisord log file +logfile_maxbytes=50MB ; maximum size of logfile before rotation +logfile_backups=2 ; number of backed up logfiles loglevel=error ; info, debug, warn, trace pidfile=/var/run/supervisord/supervisord.pid ; pidfile location nodaemon=true ; run supervisord as a daemon