From 6c6950d298d04f524356ee81423264d3b96f8783 Mon Sep 17 00:00:00 2001 From: Benjamin Brummer Date: Sat, 28 Dec 2024 08:33:39 +0100 Subject: [PATCH 1/6] frankenphp, mariadb and valkey --- debian/Dockerfile | 80 ++++++++++++----------- debian/docker-compose.yml | 131 ++++++++++++++++++++------------------ 2 files changed, 112 insertions(+), 99 deletions(-) diff --git a/debian/Dockerfile b/debian/Dockerfile index 2be80b5..20f534b 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -1,14 +1,24 @@ -FROM php:8.3-fpm AS base +FROM dunglas/frankenphp:1-php8.3-bookworm +ARG USER=ninja + +# PHP modules ARG php_require="bcmath gd pdo_mysql zip" ARG php_suggest="exif imagick intl pcntl soap saxon-12.5.0" ARG php_extra="opcache" +ENV APP_DIR=/app + +# Create a system user +RUN useradd -r ${USER} + +# Allow to bind to privileged ports +RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp + # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ mariadb-client \ gpg \ - supervisor \ # Unicode support for PDF fonts-noto-cjk-extra \ fonts-wqy-microhei \ @@ -21,14 +31,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ 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 /config/google-chrome \ + && chown ${USER}: /config/google-chrome; \ elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ apt-get install -y --no-install-recommends \ - chromium; \ + chromium \ + && mkdir /config/chromium \ + && chown ${USER}: /config/chromium; \ fi \ - # Create config directory for chromium/google-chrome-stable - && mkdir /var/www/.config \ - && chown www-data:www-data /var/www/.config \ # Cleanup && apt-get purge -y gpg \ && apt-get autoremove -y \ @@ -36,7 +47,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # Install PHP extensions -RUN ( curl -sSLf https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions -o - || echo 'return 1' ) | sh -s \ +RUN install-php-extensions \ ${php_require} \ ${php_suggest} \ ${php_extra} \ @@ -45,43 +56,38 @@ RUN ( curl -sSLf https://github.com/mlocati/docker-php-extension-installer/relea # Configure PHP RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -# Copy scripts -COPY rootfs / +# Add initialization script +COPY --chmod=0755 scripts/init.sh /usr/local/bin/init.sh -USER www-data +# Prepare app directory +RUN rm -rf ${APP_DIR}/* \ + && chown ${USER}: ${APP_DIR} -WORKDIR /var/www/html +# Create directory for artisan tinker (init.sh) +RUN mkdir /config/psysh \ + && chown ${USER}: /config/psysh + +# Change owner for caddy directories +RUN chown -R ${USER}: \ + /data/caddy \ + /config/caddy + +USER ${USER} # 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 {} \; \ + tar -oxz -C ${APP_DIR} \ + && ln -s ${APP_DIR}/resources/views/react/index.blade.php ${APP_DIR}/public/index.html \ + # Set permissions: directories 755, files 644 + && chmod -R a=r,u+w,a+X ${APP_DIR} \ # Install dependencies - && composer install --no-dev --no-scripts --no-autoloader \ - && composer dump-autoload --optimize \ - && php artisan optimize \ - && php artisan storage:link \ - # Workaround for application updates - && mv /var/www/html/public /tmp/public - -USER root - -# Setup supervisor -COPY supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf - -# Add initialization script -COPY --chmod=0755 scripts/init.sh /usr/local/bin/init.sh - -# Health check -HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ - CMD php -v || exit 1 + && composer install --working-dir=${APP_DIR} --no-dev --no-scripts --no-autoloader \ + && composer dump-autoload --working-dir=${APP_DIR} --optimize \ + && frankenphp php-cli ${APP_DIR}/artisan storage:link ENTRYPOINT ["/usr/local/bin/init.sh"] -CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] + +CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/debian/docker-compose.yml b/debian/docker-compose.yml index e643610..937ed27 100644 --- a/debian/docker-compose.yml +++ b/debian/docker-compose.yml @@ -4,93 +4,100 @@ x-logging: &default-logging max-file: "3" driver: json-file +x-app-volumes: &volumes + volumes: + - ./.env:/app/.env + - ./php/php.ini:/usr/local/etc/php/conf.d/zzz-php.ini:ro + - app_cache:/var/www/html/bootstrap/cache + - app_storage:/app/storage + services: app: build: context: . image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped - env_file: - - ./.env - volumes: - - ./.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_cache:/var/www/html/bootstrap/cache - - app_public:/var/www/html/public - - app_storage:/var/www/html/storage - networks: - - app-network - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_healthy - logging: *default-logging - - nginx: - image: nginx:alpine - restart: unless-stopped ports: - "80:80" - volumes: - - ./nginx:/etc/nginx/conf.d:ro - - app_public:/var/www/html/public:ro - - app_storage:/var/www/html/storage:ro - networks: - - app-network + env_file: + - ./.env + environment: + LARAVEL_ROLE: app + # https://frankenphp.dev/docs/production/#preparing-your-app + SERVER_NAME: :80 + <<: *volumes + # HEALTHCHECK from frankenphp image + healthcheck: + start_period: 180s depends_on: - - app + mariadb: + condition: service_healthy + valkey: + condition: service_healthy + logging: *default-logging + + app-worker: + image: invoiceninja/invoiceninja-debian:${TAG:-latest} + restart: unless-stopped + deploy: + mode: replicated + replicas: 2 + env_file: + - ./.env + environment: + LARAVEL_ROLE: worker + <<: *volumes + depends_on: + app: + condition: service_healthy logging: *default-logging - mysql: - image: mysql:8 + app-scheduler: + image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped env_file: - ./.env environment: - MYSQL_DATABASE: ${DB_DATABASE} - MYSQL_USER: ${DB_USERNAME} - MYSQL_PASSWORD: ${DB_PASSWORD} - MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} - volumes: - - mysql_data:/var/lib/mysql - networks: - - app-network - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}" ] - interval: 10s - timeout: 5s - retries: 5 + LARAVEL_ROLE: scheduler + <<: *volumes + depends_on: + app: + condition: service_healthy logging: *default-logging - redis: - image: redis:alpine + mariadb: + image: mariadb:11.4 + restart: unless-stopped + env_file: + - ./.env + environment: + MARIADB_DATABASE: ${DB_DATABASE} + MARIADB_USER: ${DB_USERNAME} + MARIADB_PASSWORD: ${DB_PASSWORD} + MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - mariadb:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 60s + logging: *default-logging + + valkey: + image: valkey/valkey:8 restart: unless-stopped volumes: - - redis_data:/data - networks: - - app-network + - valkey:/data healthcheck: - test: [ "CMD", "redis-cli", "ping" ] - interval: 10s - timeout: 5s - retries: 5 + test: [ "CMD", "valkey-cli", "ping" ] + start_period: 10s logging: *default-logging -networks: - app-network: - driver: bridge - volumes: app_cache: driver: local - app_public: - driver: local app_storage: - driver: local - mysql_data: driver: local - redis_data: + mariadb: + driver: local + valkey: driver: local From 95aec27c65f0ce4c9e35066d15a619620ac4dd79 Mon Sep 17 00:00:00 2001 From: Benjamin Brummer Date: Sat, 28 Dec 2024 08:35:19 +0100 Subject: [PATCH 2/6] Merge init scripts --- .../docker-entrypoint-init.d/10-init-in.sh | 14 --- debian/scripts/init.sh | 101 ++++++------------ 2 files changed, 31 insertions(+), 84 deletions(-) delete mode 100644 debian/rootfs/docker-entrypoint-init.d/10-init-in.sh diff --git a/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh b/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh deleted file mode 100644 index 6c03b9d..0000000 --- a/debian/rootfs/docker-entrypoint-init.d/10-init-in.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -php artisan db:seed --force - -# Build up array of arguments... -if [ ! -z "${IN_USER_EMAIL}" ]; then - email="--email ${IN_USER_EMAIL}" -fi - -if [ ! -z "${IN_PASSWORD}" ]; then - password="--password ${IN_PASSWORD}" -fi - -php artisan ninja:create-account $email $password diff --git a/debian/scripts/init.sh b/debian/scripts/init.sh index 8acc15d..b432b52 100755 --- a/debian/scripts/init.sh +++ b/debian/scripts/init.sh @@ -1,81 +1,42 @@ -#!/bin/sh -set -e +#!/bin/sh -eu -in_log() { - local type="$1" - shift - printf '%s [%s] [Entrypoint]: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$type" "$*" -} +app_dir=${APP_DIR:-/app} +role=${LARAVEL_ROLE:-app} -docker_process_init_files() { - echo - local f - for f; do - case "$f" in - *.sh) - # https://github.com/docker-library/postgres/issues/450#issuecomment-393167936 - # https://github.com/docker-library/postgres/pull/452 - if [ -x "$f" ]; then - in_log INFO "$0: running $f" - "$f" - else - in_log INFO "$0: sourcing $f" - . "$f" - fi - ;; - *) in_log INFO "$0: ignoring $f" ;; - esac - echo - done -} +if [ "$*" = 'frankenphp run --config /etc/caddy/Caddyfile --adapter caddyfile' ]; then -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" - - # 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 {} \; - - # Clear and cache config in production - if [ "$APP_ENV" = "production" ]; then - runuser -u www-data -- php artisan optimize - runuser -u www-data -- php artisan package:discover - runuser -u www-data -- php artisan migrate --force - - # 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/* + if [ "${role}" = "app" ]; then + if [ "$APP_ENV" = "production" ]; then + frankenphp php-cli "${app_dir}"/artisan optimize fi - echo "Production setup completed" - echo "IN_INIT value: $IN_INIT" + frankenphp php-cli "${app_dir}"/artisan package:discover + frankenphp php-cli "${app_dir}"/artisan migrate --force + + # If first IN run, it needs to be initialized + if [ "$(frankenphp php-cli "${app_dir}"/artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then + echo "Running initialization..." + + frankenphp php-cli "${app_dir}"/artisan db:seed --force + + if [ -n "${IN_USER_EMAIL}" ] && [ -n "${IN_PASSWORD}" ]; then + frankenphp php-cli "${app_dir}"/artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}" + else + echo "Initialization failed - Set IN_USER_EMAIL and IN_PASSWORD in .env" + exit 1 + fi + fi + echo "Production setup completed" + elif [ "${role}" = "worker" ]; then + exec frankenphp php-cli "${app_dir}"/artisan queue:work -v --sleep=3 --tries=3 --max-time=3600 + elif [ "${role}" = "scheduler" ]; then + exec frankenphp php-cli "${app_dir}"/artisan schedule:work -v + else + echo "Invalid role: ${role}" + exit 1 fi - echo "Starting supervisord..." fi exec "$@" From 068acdd097dec5733c2a11b6cfc554f396c16d10 Mon Sep 17 00:00:00 2001 From: Benjamin Brummer Date: Sat, 28 Dec 2024 08:36:19 +0100 Subject: [PATCH 3/6] remove nginx, php-fpm and supervisord configuration --- debian/nginx/invoiceninja.conf | 14 --------- debian/nginx/laravel.conf | 32 ------------------- debian/php/php-fpm.conf | 1 - debian/supervisor/supervisord.conf | 49 ------------------------------ 4 files changed, 96 deletions(-) delete mode 100644 debian/nginx/invoiceninja.conf delete mode 100644 debian/nginx/laravel.conf delete mode 100644 debian/php/php-fpm.conf delete mode 100644 debian/supervisor/supervisord.conf diff --git a/debian/nginx/invoiceninja.conf b/debian/nginx/invoiceninja.conf deleted file mode 100644 index 78add9d..0000000 --- a/debian/nginx/invoiceninja.conf +++ /dev/null @@ -1,14 +0,0 @@ -# https://nginx.org/en/docs/http/ngx_http_core_module.html -client_max_body_size 10M; -client_body_buffer_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 deleted file mode 100644 index aa02988..0000000 --- a/debian/nginx/laravel.conf +++ /dev/null @@ -1,32 +0,0 @@ -# https://laravel.com/docs/master/deployment#nginx -server { - listen 80 default_server; - server_name _; - root /var/www/html/public; - - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-Content-Type-Options "nosniff"; - - index index.php; - - charset utf-8; - - location / { - try_files $uri $uri/ /index.php?$query_string; - } - - location = /favicon.ico { access_log off; log_not_found off; } - location = /robots.txt { access_log off; log_not_found off; } - - error_page 404 /index.php; - - location ~ \.php$ { - fastcgi_pass app:9000; - fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; - include fastcgi_params; - } - - location ~ /\.(?!well-known).* { - deny all; - } -} diff --git a/debian/php/php-fpm.conf b/debian/php/php-fpm.conf deleted file mode 100644 index 63bbada..0000000 --- a/debian/php/php-fpm.conf +++ /dev/null @@ -1 +0,0 @@ -pm.max_children = 10 diff --git a/debian/supervisor/supervisord.conf b/debian/supervisor/supervisord.conf deleted file mode 100644 index ffa858a..0000000 --- a/debian/supervisor/supervisord.conf +++ /dev/null @@ -1,49 +0,0 @@ -[unix_http_server] -file=/var/run/supervisor.sock -chmod=0700 - -[supervisord] -nodaemon=true -user=root -logfile=/var/log/supervisor/supervisord.log -pidfile=/var/run/supervisord.pid - -[rpcinterface:supervisor] -supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface - -[supervisorctl] -serverurl=unix:///var/run/supervisor.sock - -[program:php-fpm] -command=/usr/local/sbin/php-fpm -F -autostart=true -autorestart=true -priority=5 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:queue-worker] -process_name=%(program_name)s_%(process_num)02d -command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600 -autostart=true -autorestart=true -stopasgroup=true -killasgroup=true -user=www-data -numprocs=2 -environment=HOME="/var/www" -stdout_logfile=/var/log/php-worker.log -stderr_logfile=/var/log/php-worker.err.log -stopwaitsecs=3600 - -[program:scheduler] -command=/bin/sh -c "while [ true ]; do (php /var/www/html/artisan schedule:run --verbose --no-interaction &); sleep 60; done" -autostart=true -autorestart=true -user=www-data -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 \ No newline at end of file From 9c96e9d261566fadcfb119cef00fcc35b8d790e8 Mon Sep 17 00:00:00 2001 From: Benjamin Brummer Date: Sat, 28 Dec 2024 09:25:25 +0100 Subject: [PATCH 4/6] make mysql and redis available/default again --- debian/docker-compose.yml | 75 +++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/debian/docker-compose.yml b/debian/docker-compose.yml index 937ed27..dbe8935 100644 --- a/debian/docker-compose.yml +++ b/debian/docker-compose.yml @@ -30,10 +30,14 @@ services: healthcheck: start_period: 180s depends_on: - mariadb: + mysql: condition: service_healthy - valkey: + redis: condition: service_healthy + # mariadb: + # condition: service_healthy + # valkey: + # condition: service_healthy logging: *default-logging app-worker: @@ -65,39 +69,74 @@ services: condition: service_healthy logging: *default-logging - mariadb: - image: mariadb:11.4 + mysql: + image: mysql:8 restart: unless-stopped env_file: - ./.env environment: - MARIADB_DATABASE: ${DB_DATABASE} - MARIADB_USER: ${DB_USERNAME} - MARIADB_PASSWORD: ${DB_PASSWORD} - MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} volumes: - - mariadb:/var/lib/mysql + - mysql_data:/var/lib/mysql healthcheck: - test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - start_period: 60s + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}" ] + interval: 10s + timeout: 5s + retries: 5 logging: *default-logging - valkey: - image: valkey/valkey:8 + redis: + image: redis:alpine restart: unless-stopped volumes: - - valkey:/data + - redis_data:/data healthcheck: - test: [ "CMD", "valkey-cli", "ping" ] - start_period: 10s + test: [ "CMD", "redis-cli", "ping" ] + interval: 10s + timeout: 5s + retries: 5 logging: *default-logging + # mariadb: + # image: mariadb:11.4 + # restart: unless-stopped + # env_file: + # - ./.env + # environment: + # MARIADB_DATABASE: ${DB_DATABASE} + # MARIADB_USER: ${DB_USERNAME} + # MARIADB_PASSWORD: ${DB_PASSWORD} + # MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + # volumes: + # - mariadb:/var/lib/mysql + # healthcheck: + # test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + # start_period: 60s + # logging: *default-logging + + # valkey: + # image: valkey/valkey:8 + # restart: unless-stopped + # volumes: + # - valkey:/data + # healthcheck: + # test: [ "CMD", "valkey-cli", "ping" ] + # start_period: 10s + # logging: *default-logging + volumes: app_cache: driver: local app_storage: driver: local - mariadb: + mysql_data: driver: local - valkey: + redis_data: driver: local + # mariadb: + # driver: local + # valkey: + # driver: local From 8f51f3bc51c94b68cf4ff2bad92ab774ffb0403b Mon Sep 17 00:00:00 2001 From: Benjamin Brummer Date: Tue, 31 Dec 2024 17:52:48 +0100 Subject: [PATCH 5/6] octane and multistage --- debian/Dockerfile | 116 ++++++++++++++++++++------------------ debian/docker-compose.yml | 12 +++- debian/scripts/init.sh | 35 ++++++++---- 3 files changed, 96 insertions(+), 67 deletions(-) diff --git a/debian/Dockerfile b/debian/Dockerfile index 20f534b..e87abfd 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -1,16 +1,40 @@ +FROM composer:latest AS composer + +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 -xz + +RUN ln -s ./resources/views/react/index.blade.php ./public/index.html + +# Set permissions: directories 755, files 644 +RUN chmod -R a=r,u+w,a+X . + +# Install dependencies +RUN composer install --no-dev --no-scripts --no-autoloader --ignore-platform-reqs + +RUN composer dump-autoload --optimize + +RUN php artisan storage:link + +# Octane +RUN php artisan octane:install --server=frankenphp + +# ================== +# InvoiceNinja image +# ================== FROM dunglas/frankenphp:1-php8.3-bookworm -ARG USER=ninja +ARG user=ninja # PHP modules ARG php_require="bcmath gd pdo_mysql zip" ARG php_suggest="exif imagick intl pcntl soap saxon-12.5.0" ARG php_extra="opcache" -ENV APP_DIR=/app - -# Create a system user -RUN useradd -r ${USER} +# Create a system user UID/GID=999 +RUN useradd -r ${user} # Allow to bind to privileged ports RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp @@ -26,19 +50,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ xfonts-wqy \ # Install google-chrome-stable(amd64)/chromium(arm64) && 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 \ - && 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 \ - && mkdir /config/google-chrome \ - && chown ${USER}: /config/google-chrome; \ + mkdir -p /etc/apt/keyrings \ + && 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 \ + && mkdir /config/google-chrome \ + && chown ${user}: /config/google-chrome; \ elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - apt-get install -y --no-install-recommends \ - chromium \ - && mkdir /config/chromium \ - && chown ${USER}: /config/chromium; \ + apt-get install -y --no-install-recommends \ + chromium \ + && mkdir /config/chromium \ + && chown ${user}: /config/chromium; \ fi \ # Cleanup && apt-get purge -y gpg \ @@ -48,46 +72,30 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install PHP extensions RUN install-php-extensions \ - ${php_require} \ - ${php_suggest} \ - ${php_extra} \ - @composer + ${php_require} \ + ${php_suggest} \ + ${php_extra} # Configure PHP -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +RUN mv "${PHP_INI_DIR}/php.ini-production" "${PHP_INI_DIR}/php.ini" + +# Create directory for artisan tinker (init.sh) +RUN mkdir /config/psysh \ + && chown ${user}: /config/psysh + +# Change owner for caddy directories +RUN chown -R ${user}: \ + /data/caddy \ + /config/caddy + +ENTRYPOINT ["/usr/local/bin/init.sh"] + +CMD ["frankenphp", "php-cli", "artisan", "octane:frankenphp"] + +# InvoiceNinja +COPY --from=composer --chown=${user}:${user} /app /app # Add initialization script COPY --chmod=0755 scripts/init.sh /usr/local/bin/init.sh -# Prepare app directory -RUN rm -rf ${APP_DIR}/* \ - && chown ${USER}: ${APP_DIR} - -# Create directory for artisan tinker (init.sh) -RUN mkdir /config/psysh \ - && chown ${USER}: /config/psysh - -# Change owner for caddy directories -RUN chown -R ${USER}: \ - /data/caddy \ - /config/caddy - -USER ${USER} - -# 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 ${APP_DIR} \ - && ln -s ${APP_DIR}/resources/views/react/index.blade.php ${APP_DIR}/public/index.html \ - # Set permissions: directories 755, files 644 - && chmod -R a=r,u+w,a+X ${APP_DIR} \ - # Install dependencies - && composer install --working-dir=${APP_DIR} --no-dev --no-scripts --no-autoloader \ - && composer dump-autoload --working-dir=${APP_DIR} --optimize \ - && frankenphp php-cli ${APP_DIR}/artisan storage:link - -ENTRYPOINT ["/usr/local/bin/init.sh"] - -CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] +USER ${user} diff --git a/debian/docker-compose.yml b/debian/docker-compose.yml index dbe8935..2d14dc6 100644 --- a/debian/docker-compose.yml +++ b/debian/docker-compose.yml @@ -1,3 +1,5 @@ +# name: invoiceninja + x-logging: &default-logging options: max-size: "10m" @@ -17,14 +19,14 @@ services: context: . image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped + # php artisan help octane:frankenphp + command: --log-level=info ports: - - "80:80" + - "80:8000" env_file: - ./.env environment: LARAVEL_ROLE: app - # https://frankenphp.dev/docs/production/#preparing-your-app - SERVER_NAME: :80 <<: *volumes # HEALTHCHECK from frankenphp image healthcheck: @@ -43,6 +45,8 @@ services: app-worker: image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped + # php artisan help queue:work + command: --verbose --sleep=3 --tries=3 --max-time=3600 deploy: mode: replicated replicas: 2 @@ -59,6 +63,8 @@ services: app-scheduler: image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped + # php artisan help schedule:work + command: --verbose env_file: - ./.env environment: diff --git a/debian/scripts/init.sh b/debian/scripts/init.sh index b432b52..bb6ab65 100755 --- a/debian/scripts/init.sh +++ b/debian/scripts/init.sh @@ -1,42 +1,57 @@ #!/bin/sh -eu -app_dir=${APP_DIR:-/app} +# Fallback to app role=${LARAVEL_ROLE:-app} -if [ "$*" = 'frankenphp run --config /etc/caddy/Caddyfile --adapter caddyfile' ]; then +# Check for default CMD, flag(s) or empty CMD +if [ "$*" = 'frankenphp php-cli artisan octane:frankenphp' ] || [ "${1#-}" != "$1" ] || [ "$#" -eq "0" ]; then + # Run app if [ "${role}" = "app" ]; then + cmd="frankenphp php-cli artisan octane:frankenphp" + if [ "$APP_ENV" = "production" ]; then - frankenphp php-cli "${app_dir}"/artisan optimize + frankenphp php-cli artisan optimize fi - frankenphp php-cli "${app_dir}"/artisan package:discover + frankenphp php-cli artisan package:discover - frankenphp php-cli "${app_dir}"/artisan migrate --force + # Run migrations (if any) + frankenphp php-cli artisan migrate --force # If first IN run, it needs to be initialized - if [ "$(frankenphp php-cli "${app_dir}"/artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then + if [ "$(frankenphp php-cli artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then echo "Running initialization..." - frankenphp php-cli "${app_dir}"/artisan db:seed --force + frankenphp php-cli artisan db:seed --force if [ -n "${IN_USER_EMAIL}" ] && [ -n "${IN_PASSWORD}" ]; then - frankenphp php-cli "${app_dir}"/artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}" + frankenphp php-cli artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}" else echo "Initialization failed - Set IN_USER_EMAIL and IN_PASSWORD in .env" exit 1 fi fi + echo "Production setup completed" + # Run worker elif [ "${role}" = "worker" ]; then - exec frankenphp php-cli "${app_dir}"/artisan queue:work -v --sleep=3 --tries=3 --max-time=3600 + cmd="frankenphp php-cli artisan queue:work" + # Run scheduler elif [ "${role}" = "scheduler" ]; then - exec frankenphp php-cli "${app_dir}"/artisan schedule:work -v + cmd="frankenphp php-cli artisan schedule:work" + # Invalid role else echo "Invalid role: ${role}" exit 1 fi + # Append flag(s) to role cmd + if [ "${1#-}" != "$1" ]; then + set -- ${cmd} "$@" + else + set -- ${cmd} + fi fi exec "$@" From f680c8a59a76a1bc20036639d805dff4265cb114 Mon Sep 17 00:00:00 2001 From: Benjamin Brummer Date: Tue, 31 Dec 2024 17:57:54 +0100 Subject: [PATCH 6/6] require laravel/octane --- debian/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/Dockerfile b/debian/Dockerfile index e87abfd..9f089ea 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -14,6 +14,8 @@ RUN chmod -R a=r,u+w,a+X . # Install dependencies RUN composer install --no-dev --no-scripts --no-autoloader --ignore-platform-reqs +RUN composer require laravel/octane --ignore-platform-reqs + RUN composer dump-autoload --optimize RUN php artisan storage:link