Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/bake.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
prepare
strategy:
matrix:
target: [app, scheduler, worker]
target: [aio, app, scheduler, worker]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
- build
strategy:
matrix:
target: [app, scheduler, worker]
target: [aio, app, scheduler, worker]

steps:
- name: Download meta bake definition
Expand Down
28 changes: 19 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ ARG php_extra="opcache"
ARG saxon_edition="HE"

# Create a system user UID/GID=999
RUN useradd -r ${user}
RUN groupadd -g 999 ${user} && \
useradd -u 999 -g ${user} -r ${user}

# Allow to bind to privileged ports
RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp
Expand Down Expand Up @@ -77,25 +78,34 @@ COPY --from=prepare-app --chown=${user}:${user} /app /app
# Add initialization script
COPY --chmod=0755 ./entrypoint.sh /usr/local/bin/entrypoint.sh

USER ${user}

ENV FILESYSTEM_DISK=debian_docker
ENV IS_DOCKER=true
ENV SNAPPDF_CHROMIUM_PATH=/usr/bin/chromium

ENTRYPOINT ["entrypoint.sh"]

FROM base AS aio
ENV LARAVEL_ROLE=aio
HEALTHCHECK --start-period=100s CMD curl -f http://localhost/health || exit 1
RUN apt-get update && apt-get install -y --no-install-recommends supervisor && rm -rf /var/lib/apt/lists/*
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]

FROM base AS app
# STOPSIGNAL SIGQUIT
ENV LARAVEL_ROLE=app
HEALTHCHECK --start-period=100s CMD curl -f http://localhost/health
CMD ["frankenphp", "php-cli", "artisan", "octane:frankenphp"]
USER ${user}
HEALTHCHECK --start-period=100s CMD curl -f http://localhost/health || exit 1
CMD ["php", "artisan", "octane:frankenphp"]

FROM base AS scheduler
ENV LARAVEL_ROLE=scheduler
HEALTHCHECK --start-period=10s CMD pgrep -f schedule:work
CMD ["frankenphp", "php-cli", "artisan", "schedule:work"]
USER ${user}
HEALTHCHECK --start-period=10s CMD pgrep -f schedule:work || exit 1
CMD ["php", "artisan", "schedule:work", "--no-interaction"]

FROM base AS worker
ENV LARAVEL_ROLE=worker
HEALTHCHECK --start-period=10s CMD pgrep -f queue:work
CMD ["frankenphp", "php-cli", "artisan", "queue:work"]
USER ${user}
HEALTHCHECK --start-period=10s CMD pgrep -f queue:work || exit 1
CMD ["php", "artisan", "queue:work", "--no-interaction"]
47 changes: 47 additions & 0 deletions compose/aio-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: invoiceninja

services:
aio:
image: benbrummer/invoiceninja:latest-aio
restart: unless-stopped
ports:
- "8012:80" # HTTP
env_file:
- ./.env
volumes:
- app_storage:/app/storage
- caddy_data:/data
# - ./php/php.ini:/usr/local/etc/php/conf.d/invoiceninja.ini
depends_on:
mariadb:
condition: service_healthy
valkey:
condition: service_healthy
# tty: true

mariadb:
image: mariadb:11.8
restart: unless-stopped
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" ]

valkey:
image: valkey/valkey:9
restart: unless-stopped
volumes:
- valkey:/data
healthcheck:
test: [ "CMD", "valkey-cli", "ping" ]

volumes:
app_storage:
caddy_data:
mariadb:
valkey:
3 changes: 2 additions & 1 deletion sample.compose.yaml → compose/multi-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ services:
app:
image: benbrummer/invoiceninja:5-app
restart: unless-stopped
stop_grace_period: 30s
command: --port=80 --workers=2
# command: --host=example.com --port=443 --workers=2 --https --http-redirect --log-level=info
ports:
- "80:80" # HTTP
- "8012:80" # HTTP
# - "443:443" # HTTPS
# - "443:443/udp" # HTTP/3, Works for chromium based browser, but causes H3_GENERAL_PROTOCOL_ERROR for pdf previews in Firefox
env_file:
Expand Down
File renamed without changes.
8 changes: 8 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ variable "MINOR" {

group "default" {
targets = [
"aio",
"app",
"scheduler",
"worker"
Expand All @@ -43,6 +44,13 @@ target _common {
pull = true
}

target "aio" {
description = "AIO for Invoiceninja Application Image"
inherits = ["_common"]
tags = [for tag in target._common.tags : replace(tag, "-", "-aio")]
target = "aio"
}

target "app" {
description = "Invoiceninja Application Image"
inherits = ["_common"]
Expand Down
135 changes: 78 additions & 57 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,79 +1,100 @@
#!/bin/sh -eu

if [ "--help" = "$1" ]; then
echo [FLAGS]
echo The CMD defined can be extended with flags for artisan commands
echo
echo Available flags can be displaced:
echo docker run --rm benbrummer/invoiceninja:5-octane frankenphp php-cli artisan help octane:frankenphp
echo docker run --rm benbrummer/invoiceninja:5-octane-worker frankenphp php-cli artisan help queue:work
echo docker run --rm benbrummer/invoiceninja:5-octane-scheduler frankenphp php-cli artisan help schedule:work
echo
echo Example:
echo docker run benbrummer/invoiceninja:5-octane-worker --verbose --sleep=3 --tries=3 --max-time=3600
echo
echo [Deployment]
echo Docker compose is recommended
echo
echo Example:
echo https://github.com/benbrummer/dockerfiles/blob/octane-action/sample.compose.yaml
echo
exit 0
fi

case "${LARAVEL_ROLE}" in
app)
if [ "$*" = 'frankenphp php-cli artisan octane:frankenphp' ] || [ "${1#-}" != "$1" ]; then
cmd="frankenphp php-cli artisan octane:frankenphp"
#!/bin/bash -eu

# --- ROLE: aio (Runs as root) ---
if [ "${LARAVEL_ROLE}" = 'aio' ]; then
# Clear and cache config in production
if [ "$*" = 'supervisord -c /etc/supervisor/supervisord.conf' ]; then
if [ "$APP_ENV" = "production" ]; then
frankenphp php-cli artisan migrate --force
frankenphp php-cli artisan cache:clear # Clear after the migration
frankenphp php-cli artisan ninja:design-update
frankenphp php-cli artisan optimize
echo "Running production setup..."
runuser -u ninja -- php artisan migrate --force
runuser -u ninja -- php artisan cache:clear
runuser -u ninja -- php artisan ninja:design-update
runuser -u ninja -- php artisan optimize

# If first IN run, it needs to be initialized
if [ "$(frankenphp php-cli artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then
# Check if initialization is needed
if [ "$(runuser -u ninja -- php artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then
echo "Running initialization..."

frankenphp php-cli artisan db:seed --force

runuser -u ninja -- php artisan db:seed --force
if [ -n "${IN_USER_EMAIL}" ] && [ -n "${IN_PASSWORD}" ]; then
frankenphp php-cli artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}"
runuser -u ninja -- php 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
fi
fi
;;
echo "Handing off to supervisord..."
# Fall through to exec "$@" at the bottom

scheduler)
if [ "$*" = 'frankenphp php-cli artisan schedule:work' ] || [ "${1#-}" != "$1" ]; then
cmd="frankenphp php-cli artisan schedule:work"
# --- ROLES: app, worker, scheduler (Run as ninja) ---
else
if [ "--help" = "$1" ]; then
echo [FLAGS]
echo The CMD defined can be extended with flags for artisan commands
echo
echo Available flags can be displaced:
echo docker run --rm benbrummer/invoiceninja:5-app php artisan help octane:frankenphp
echo docker run --rm benbrummer/invoiceninja:5-worker php artisan help queue:work
echo docker run --rm benbrummer/invoiceninja:5-scheduler php artisan help schedule:work
echo
echo Example:
echo docker run benbrummer/invoiceninja:5-worker --verbose --sleep=3 --tries=3 --max-time=3600
echo
echo [Deployment]
echo Docker compose is recommended
echo
echo Example:
echo https://github.com/benbrummer/dockerfiles/blob/main/sample.compose.yaml
echo
exit 0
fi

if [ "$APP_ENV" = "production" ]; then
frankenphp php-cli artisan optimize
fi
;;
case "${LARAVEL_ROLE}" in
app)
# Check if we should prepend the octane command
if [ $# -eq 0 ] || [[ "$1" == -* ]] || [ "$*" = "php artisan octane:frankenphp" ]; then
if [ "$APP_ENV" = "production" ]; then
echo "Running production setup..."
php artisan migrate --force
php artisan cache:clear
php artisan ninja:design-update
php artisan optimize

worker)
if [ "$*" = 'frankenphp php-cli artisan queue:work' ] || [ "${1#-}" != "$1" ]; then
cmd="frankenphp php-cli artisan queue:work"
fi
if [ "$(php artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then
echo "Running initialization..."
php artisan db:seed --force
if [ -n "${IN_USER_EMAIL:-}" ] && [ -n "${IN_PASSWORD:-}" ]; then
php artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}"
fi
fi
fi

if [ "$APP_ENV" = "production" ]; then
frankenphp php-cli artisan optimize
fi
;;
# CRITICAL FIX: Prepend the base command if only flags were passed
if [ $# -eq 0 ] || [[ "$1" == -* ]]; then
set -- php artisan octane:frankenphp "$@"
fi
fi
;;

esac
scheduler)
[ "$APP_ENV" = "production" ] && php artisan optimize
echo "Starting scheduler ..."

if [ $# -eq 0 ] || [[ "$1" == -* ]]; then
set -- php artisan queue:work --no-interaction "$@"
fi
;;

worker)
[ "$APP_ENV" = "production" ] && php artisan optimize
echo "Starting worker..."
if [ $# -eq 0 ] || [[ "$1" == -* ]]; then
set -- php artisan queue:work --no-interaction "$@"
fi
;;
esac

# Append flag(s) to role cmd
if [ "${1#-}" != "$1" ] && [ -n "$cmd" ]; then
set -- ${cmd} "$@"
fi

exec "$@"
47 changes: 47 additions & 0 deletions supervisord.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[supervisord]
nodaemon=true
user=root
pidfile=/var/run/supervisord.pid
logfile=/dev/null
logfile_maxbytes=0

[program:app]
command=php artisan octane:frankenphp --port=80 --workers=2
user=ninja
priority=5
autostart=true
autorestart=true
startretries=3
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
stopasgroup=true
killasgroup=true

[program:worker]
process_name=%(program_name)s_%(process_num)02d
command=php artisan queue:work --sleep=3 --tries=3 --max-time=3600
user=ninja
numprocs=2
priority=10
autostart=true
autorestart=true
startretries=3
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
stopasgroup=true
killasgroup=true

[program:scheduler]
command=php artisan schedule:work
user=ninja
priority=15
autostart=true
autorestart=true
startretries=3
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
stopasgroup=true
killasgroup=true
33 changes: 21 additions & 12 deletions version.sh
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
#!/bin/bash
export VERSION="$(cat version.txt | tr -d v)"
export MAJOR="$(echo "${VERSION}" | cut -d. -f1)"
export MINOR="${MAJOR}.$(echo "${VERSION}" | cut -d. -f2)"
export URL="https://github.com/invoiceninja/invoiceninja/releases/download/v${VERSION}/invoiceninja.tar.gz"
VERSION="$(cat version.txt | tr -d v)"
export VERSION

MAJOR="$(echo "${VERSION}" | cut -d. -f1)"
export MAJOR

MINOR="${MAJOR}.$(echo "${VERSION}" | cut -d. -f2)"
export MINOR

URL="https://github.com/invoiceninja/invoiceninja/releases/download/v${VERSION}/invoiceninja.tar.gz"
export URL

echo "Current version: ${VERSION}"

if [ "${GITHUB_ACTIONS}" ]; then
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
echo "MAJOR=${MAJOR}" >> "${GITHUB_ENV}"
echo "MINOR=${MINOR}" >> "${GITHUB_ENV}"
echo "URL=${URL}" >> "${GITHUB_ENV}"
echo "VERSION=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "MAJOR=${MAJOR}" >> "${GITHUB_OUTPUT}"
echo "MINOR=${MINOR}" >> "${GITHUB_OUTPUT}"
echo "URL=${URL}" >> "${GITHUB_OUTPUT}"
{
echo "VERSION=${VERSION}"
echo "MAJOR=${MAJOR}"
echo "MINOR=${MINOR}"
echo "URL=${URL}"
echo "VERSION=${VERSION}"
echo "MAJOR=${MAJOR}"
echo "MINOR=${MINOR}"
echo "URL=${URL}"
} >>"${GITHUB_OUTPUT}"
fi