diff --git a/debian/Dockerfile b/debian/Dockerfile index e106d26..c55687b 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -1,89 +1,75 @@ FROM php:8.3-fpm AS base -ARG saxon=12.5.0 +ARG php_require="bcmath gd pdo_mysql zip" +ARG php_suggest="exif imagick intl pcntl soap" +ARG php_suggest_debian="saxon-12.5.0" +ARG php_extra="opcache" # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - gnupg2 \ + default-mysql-client \ + gpg \ gosu \ supervisor \ - default-mysql-client \ - fonts-liberation \ - fonts-noto-cjk \ + # Unicode support for PDF fonts-noto-cjk-extra \ fonts-wqy-microhei \ fonts-wqy-zenhei \ - libasound2 \ - libatk-bridge2.0-0 \ - libatk1.0-0 \ - libatspi2.0-0 \ - libcups2 \ - libdbus-1-3 \ - libdrm2 \ - libgbm1 \ - libgtk-3-0 \ - libnspr4 \ - libnss3 \ - libonig-dev \ - libpng-dev \ - libwayland-client0 \ - libxcomposite1 \ - libxdamage1 \ - libxfixes3 \ - libxkbcommon0 \ - libxml2-dev \ - libxrandr2 \ - xdg-utils \ xfonts-wqy \ && if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ mkdir -p /etc/apt/keyrings \ - && curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /etc/apt/keyrings/google.gpg \ + && curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | \ + gpg --dearmor -o /etc/apt/keyrings/google.gpg \ && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google.gpg] https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ && apt-get update \ - && apt-get install -y --no-install-recommends google-chrome-stable; \ + && apt-get install -y --no-install-recommends google-chrome-stable \ + && mkdir -p /var/www/.config/google-chrome \ + && chown -R www-data:www-data /var/www/.config/google-chrome; \ + elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ + apt-get install -y --no-install-recommends \ + # Packages for chrome + fonts-liberation \ + libasound2 \ + libatk-bridge2.0-0 \ + libatk1.0-0 \ + libatspi2.0-0 \ + libcups2 \ + libdbus-1-3 \ + libdrm2 \ + libgbm1 \ + libgtk-3-0 \ + libnspr4 \ + libnss3 \ + libwayland-client0 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxkbcommon0 \ + libxrandr2 \ + xdg-utils \ + && mkdir -p /var/www/.chrome/chrome-profile \ + && chown -R www-data:www-data /var/www/.chrome/chrome-profile; \ fi \ + && apt-get purge -y gpg \ + && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Set permissions for www-data to execute -RUN mkdir -p /var/www/.chrome/chrome-profile \ - && chown -R www-data:www-data /var/www/.chrome \ - && chmod -R 755 /var/www/.chrome \ - && if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ - chown root:root /usr/bin/google-chrome \ - && chmod 4755 /usr/bin/google-chrome \ - && chown -R root:root /opt/google/chrome \ - && chmod -R 755 /opt/google/chrome; \ - fi \ - && chown -R www-data:www-data /var/www - -# Create required directories with proper permissions -RUN mkdir -p /tmp/chrome \ - && if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ - chown -R www-data:www-data /tmp/chrome \ - && chmod -R 755 /tmp/chrome; \ - fi - -# Copy Install PHP extensions installer -ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ - # Install Required PHP extensions. -RUN install-php-extensions \ - bcmath \ - exif \ - gd \ - gmp \ - imagick \ - mysqli \ - opcache \ - pcntl \ - pdo_mysql \ - redis \ - saxon-${saxon} \ - soap \ - zip \ - @composer +RUN if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ + ( curl -sSLf https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions -o - || echo 'return 1' ) | sh -s \ + ${php_require} \ + ${php_suggest} \ + ${php_suggest_debian} \ + ${php_extra} \ + @composer; \ + elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ + ( curl -sSLf https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions -o - || echo 'return 1' ) | sh -s \ + ${php_require} \ + ${php_suggest} \ + ${php_extra} \ + @composer; \ + fi # Configure PHP RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" @@ -91,25 +77,28 @@ RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" # Copy scripts COPY rootfs / -# Set working directory -WORKDIR /var/www/html - -# Download and extract application -RUN set -eux; \ - DOWNLOAD_URL=$(curl -s "https://api.github.com/repos/invoiceninja/invoiceninja/releases/latest" | \ - grep -o '"browser_download_url": "[^"]*invoiceninja.tar"' | cut -d '"' -f 4) && \ - curl -L "$DOWNLOAD_URL" | tar -xvz -C /var/www/html && \ - chown -R www-data:www-data /var/www/html - USER www-data -# Install dependencies -RUN composer install --no-dev --no-scripts --no-autoloader +WORKDIR /var/www/html -# Generate optimized autoloader and clear cache -RUN composer dump-autoload --optimize \ +# Setup InvoiceNinja +RUN curl -s "https://api.github.com/repos/invoiceninja/invoiceninja/releases/latest" | \ + grep -o '"browser_download_url": "[^"]*invoiceninja.tar"' | \ + cut -d '"' -f 4 | \ + xargs curl -sL | \ + tar -oxz -C /var/www/html \ + && cp /var/www/html/resources/views/react/index.blade.php /var/www/html/public/index.html \ + # File permissions + && find /var/www/html/ -type f -exec chmod 644 {} \; \ + # Directory permissions + && find /var/www/html/ -type d -exec chmod 755 {} \; \ + # Install dependencies + && composer install --no-dev --no-scripts --no-autoloader \ + && composer dump-autoload --optimize \ && php artisan optimize \ - && php artisan storage:link + && php artisan storage:link \ + # Workaround for application updates + && mv /var/www/html/public /tmp/public USER root @@ -119,40 +108,6 @@ COPY supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf # Add initialization script COPY --chmod=0755 scripts/init.sh /usr/local/bin/init.sh -# Configure PHP-FPM -RUN sed -i "s/user = www-data/user = www-data/g" /usr/local/etc/php-fpm.d/www.conf \ - && sed -i "s/group = www-data/group = www-data/g" /usr/local/etc/php-fpm.d/www.conf - -# Create volume directories -RUN mkdir -p \ - /var/www/html/storage/app/public \ - /var/www/html/storage/framework/cache \ - /var/www/html/storage/framework/sessions \ - /var/www/html/storage/framework/views \ - /var/www/html/storage/logs \ - /var/www/html/public/uploads \ - /var/run \ - /var/log/supervisor - -RUN cp /var/www/html/resources/views/react/index.blade.php /var/www/html/public/index.html - -# copy the public/ directory into a separate folder so that we can overwrite the volume later via the entrypoint -RUN mkdir -p /image-original/public && cp -r /var/www/html/public /image-original/public - -# Set permissions -RUN chown -R www-data:www-data \ - /var/www/html/storage \ - /var/www/html/bootstrap/cache \ - /var/www/html/public/uploads \ - /var/run \ - /var/log/supervisor \ - && chmod -R 775 \ - /var/www/html/public/uploads \ - /var/www/html/storage \ - /var/www/html/bootstrap/cache \ - /var/run \ - /var/log/supervisor - # Health check HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD php -v || exit 1 @@ -160,4 +115,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ EXPOSE 9000 ENTRYPOINT ["/usr/local/bin/init.sh"] -CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] +CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/debian/docker-compose.yml b/debian/docker-compose.yml index f00bc1d..e643610 100644 --- a/debian/docker-compose.yml +++ b/debian/docker-compose.yml @@ -13,14 +13,13 @@ services: env_file: - ./.env volumes: - - ./scripts/init.sh:/usr/local/bin/init.sh - - ./.env:/var/www/html/.env:ro + - ./.env:/var/www/html/.env - ./php/php.ini:/usr/local/etc/php/conf.d/zzz-php.ini:ro - ./php/php-fpm.conf:/usr/local/etc/php-fpm.d/zzz-php-fpm.conf:ro - ./supervisor/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf:ro - - app_storage:/var/www/html/storage - app_cache:/var/www/html/bootstrap/cache - - image_public:/var/www/html/public + - app_public:/var/www/html/public + - app_storage:/var/www/html/storage networks: - app-network depends_on: @@ -37,8 +36,8 @@ services: - "80:80" volumes: - ./nginx:/etc/nginx/conf.d:ro + - app_public:/var/www/html/public:ro - app_storage:/var/www/html/storage:ro - - image_public:/var/www/html/public networks: - app-network depends_on: @@ -46,7 +45,7 @@ services: logging: *default-logging mysql: - image: mysql:8.0 + image: mysql:8 restart: unless-stopped env_file: - ./.env @@ -60,16 +59,7 @@ services: networks: - app-network healthcheck: - test: - [ - "CMD", - "mysqladmin", - "ping", - "-h", - "localhost", - "-u${MYSQL_USER}", - "-p${MYSQL_PASSWORD}", - ] + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}" ] interval: 10s timeout: 5s retries: 5 @@ -83,7 +73,7 @@ services: networks: - app-network healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: [ "CMD", "redis-cli", "ping" ] interval: 10s timeout: 5s retries: 5 @@ -94,13 +84,13 @@ networks: driver: bridge volumes: - app_storage: - driver: local app_cache: driver: local + app_public: + driver: local + app_storage: + driver: local mysql_data: driver: local redis_data: driver: local - image_public: - driver: local diff --git a/debian/nginx/invoiceninja.conf b/debian/nginx/invoiceninja.conf index 8e4438c..e133344 100644 --- a/debian/nginx/invoiceninja.conf +++ b/debian/nginx/invoiceninja.conf @@ -1,9 +1,13 @@ -client_max_body_size 100M; -client_body_buffer_size 100M; - -fastcgi_buffer_size 16k; -fastcgi_buffers 4 16k; - -gzip on; - +# https://nginx.org/en/docs/http/ngx_http_core_module.html +client_max_body_size 10M; server_tokens off; + +# https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html +fastcgi_buffers 32 16K; + +# https://nginx.org/en/docs/http/ngx_http_gzip_module.html +gzip on; +gzip_comp_level 2; +gzip_min_length 1M; +gzip_proxied any; +gzip_types *; diff --git a/debian/nginx/laravel.conf b/debian/nginx/laravel.conf index 88ffb39..aa02988 100644 --- a/debian/nginx/laravel.conf +++ b/debian/nginx/laravel.conf @@ -1,3 +1,4 @@ +# https://laravel.com/docs/master/deployment#nginx server { listen 80 default_server; server_name _; @@ -23,8 +24,6 @@ server { fastcgi_pass app:9000; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; - fastcgi_buffer_size 16k; - fastcgi_buffers 4 16k; } location ~ /\.(?!well-known).* { diff --git a/debian/php/php-fpm.conf b/debian/php/php-fpm.conf index aa4e660..63bbada 100644 --- a/debian/php/php-fpm.conf +++ b/debian/php/php-fpm.conf @@ -1,2 +1 @@ -[www] pm.max_children = 10 diff --git a/debian/php/php.ini b/debian/php/php.ini index 12cf380..35c9074 100644 --- a/debian/php/php.ini +++ b/debian/php/php.ini @@ -1,14 +1,15 @@ -memory_limit=512M +[core] +# https://www.php.net/manual/en/ini.core.php +post_max_size = 10M +upload_max_filesize = 10M -opcache.enable=1 -opcache.preload=/var/www/html/preload.php -opcache.preload_user=www-data -opcache.max_accelerated_files=300000 -opcache.validate_timestamps=1 -opcache.revalidate_freq=30 -opcache.jit_buffer_size=256M -opcache.jit=1205 -opcache.memory_consumption=1024M +[opcache] +# https://www.php.net/manual/en/opcache.installation.php#opcache.installation.recommended +opcache.enable_cli=1 +opcache.max_accelerated_files=4000 +opcache.revalidate_freq=60 -post_max_size = 60M -upload_max_filesize = 50M +[jit] +# https://wiki.php.net/rfc/jit_config_defaults +opcache.jit=tracing +opcache.jit_buffer_size=64M diff --git a/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh b/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh index b744011..6c03b9d 100644 --- a/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh +++ b/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh @@ -11,4 +11,4 @@ if [ ! -z "${IN_PASSWORD}" ]; then password="--password ${IN_PASSWORD}" fi -php artisan ninja:create-account $email $password \ No newline at end of file +php artisan ninja:create-account $email $password diff --git a/debian/scripts/init.sh b/debian/scripts/init.sh index d231146..25b0727 100755 --- a/debian/scripts/init.sh +++ b/debian/scripts/init.sh @@ -1,10 +1,10 @@ #!/bin/sh set -e - in_log() { - local type="$1"; shift - printf '%s [%s] [Entrypoint]: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$type" "$*" + local type="$1" + shift + printf '%s [%s] [Entrypoint]: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$type" "$*" } docker_process_init_files() { @@ -29,64 +29,53 @@ docker_process_init_files() { done } -# Create directories if they don't exist -mkdir -p \ - /var/www/html/storage/app/public \ - /var/www/html/storage/framework/cache \ - /var/www/html/storage/framework/sessions \ - /var/www/html/storage/framework/views \ - /var/www/html/storage/logs \ - /var/www/html/public/storage +if [ "$*" = 'supervisord -c /etc/supervisor/conf.d/supervisord.conf' ]; then + # Workaround for application updates + if [ "$(ls -A /tmp/public)" ]; then + echo "Updating public folder..." + rm -rf /var/www/html/public/.htaccess \ + /var/www/html/public/.well-known \ + /var/www/html/public/* + mv /tmp/public/* \ + /tmp/public/.htaccess \ + /tmp/public/.well-known \ + /var/www/html/public/ + fi + echo "Public Folder is up to date" -# Set directory permissions without changing ownership -chmod -R 775 \ - /var/www/html/storage \ - /var/www/html/bootstrap/cache \ - /var/www/html/public/storage + # Ensure owner, file and directory permissions are correct + chown -R www-data:www-data \ + /var/www/html/public \ + /var/www/html/storage + find /var/www/html/public \ + /var/www/html/storage \ + -type f -exec chmod 644 {} \; + find /var/www/html/public \ + /var/www/html/storage \ + -type d -exec chmod 755 {} \; -chown -R www-data:www-data /var/www/html/storage + # Clear and cache config in production + if [ "$APP_ENV" = "production" ]; then + gosu www-data php artisan optimize + gosu www-data php artisan package:discover + gosu www-data php artisan migrate --force -# Ensure symlink for storage/app/public -if [ ! -L /var/www/html/public/storage ]; then - echo "Creating symlink for storage/app/public..." - ln -sfn /var/www/html/storage/app/public /var/www/html/public/storage -fi + # If first IN run, it needs to be initialized + echo "Checking initialization status..." + IN_INIT=$(php artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();') + echo "IN_INIT value: $IN_INIT" -# Clean the existing public/ directory but exclude .js and .css files -if [ -d /var/www/html/public ]; then - echo "Cleaning up .js and .css files in public/ directory..." - find /var/www/html/public -type f \( -name '*.js' -o -name '*.css' \) -exec rm -f {} \; -fi + if [ "$IN_INIT" = "1" ]; then + echo "Running initialization scripts..." + docker_process_init_files /docker-entrypoint-init.d/* + fi -# Copy the public/ directory from the image to the mounted volume -echo "Copying public/ directory from image to volume..." -cp -r /image-original/public/* /var/www/html/ + echo "Production setup completed" + echo "IN_INIT value: $IN_INIT" - -# Clear and cache config in production -if [ "$APP_ENV" = "production" ]; then - gosu www-data php artisan config:cache - gosu www-data php artisan optimize - gosu www-data php artisan package:discover - gosu www-data php artisan migrate --force - - echo "Checking initialization status..." - - # If first IN run, it needs to be initialized - echo "Checking initialization status..." - IN_INIT=$(php artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();') - echo "IN_INIT value: $IN_INIT" - - if [ "$IN_INIT" = "1" ]; then - echo "Running initialization scripts..." - docker_process_init_files /docker-entrypoint-init.d/* fi - echo "Production setup completed" - echo "IN_INIT value: $IN_INIT" - + echo "Starting supervisord..." fi -echo "Starting supervisord..." -# Start supervisord in the foreground -exec /usr/bin/supervisord -n -c /etc/supervisor/conf.d/supervisord.conf \ No newline at end of file +exec "$@"