🚢 Simplified docker builds (#456)

* Simplify docker builds

* Ignore docker and frontend scripts from PHP related checks

* Update docker development setup
This commit is contained in:
Darko Gjorgjijoski
2025-08-31 15:07:22 +02:00
committed by GitHub
parent 3ed91545d1
commit 23f6b1877f
16 changed files with 569 additions and 2051 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,28 @@
.dev/
.idea/
.git/
.github/
database/*.sqlite
docker/
!docker/production/entrypoint.d
!docker/production/inject.sh
node_modules/
database/*.sqlite
storage/app/*
!storage/app/templates*
storage/fonts/*
storage/framework/cache/data/*
storage/framework/sessions/*
storage/framework/views/*
storage/logs/*
tests/
vendor/
.dockerignore
.editorconfig
.env
.env.example
.env.testing
.eslintrc.mjs
.gitattributes
@@ -33,4 +40,3 @@ invoiceshelf.code-workspace
package-lock.json
phpunit.xml
readme.md
version.md

View File

@@ -7,6 +7,10 @@ on:
- '**/*.md'
- 'public/build/*.js'
- 'public/build/**/*.js'
- 'docker/**/*'
- 'resources/scripts/**/*'
tags-ignore:
- "*"
branches-ignore:
- 'translations'
pull_request:
@@ -93,52 +97,3 @@ jobs:
- name: Apply tests ${{ matrix.php-version }}
run: php artisan test
createReleaseFile:
name: 3⃣ Build / Upload - Release File
if: github.ref_type == 'tag'
needs:
- tests
runs-on: ubuntu-latest
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: ${{ env.extensions }}
coverage: none
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
with:
composer-options: --no-dev
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install
run: npm install
- name: Compile Front-end
run: npm run build
- name: Build Dist
run: |
make clean dist
- name: Upload package
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ github.token }}
file: InvoiceShelf.zip
asset_name: InvoiceShelf.zip
tag: ${{ github.ref }}
overwrite: true

261
.github/workflows/docker.yaml vendored Normal file
View File

@@ -0,0 +1,261 @@
name: Docker Build and Push
on:
release:
types: [published]
schedule:
# Run nightly at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
tag:
description: 'Docker tag'
required: true
default: 'latest'
jobs:
php_syntax_errors:
name: 1⃣ PHP Code Style errors
if: github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
uses: ramsey/composer-install@v2
- name: Check source code for syntax errors
run: ./vendor/bin/pint --test
tests:
name: 2⃣ PHP ${{ matrix.php-version }} Tests
if: github.event_name == 'release'
needs:
- php_syntax_errors
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- 8.2
- 8.3
- 8.4
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP Action
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: ${{ env.extensions }}
coverage: xdebug
tools: pecl, composer
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install
run: npm install
- name: Compile Front-end
run: npm run build
- name: Apply tests ${{ matrix.php-version }}
run: php artisan test
release_artifact_build:
name: 🏗️ Build / Upload - Release File
if: github.event_name == 'release'
needs:
- tests
runs-on: ubuntu-latest
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: ${{ env.extensions }}
coverage: none
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
with:
composer-options: --no-dev
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install
run: npm install
- name: Compile Front-end
run: npm run build
- name: Build Dist
run: |
make clean dist
- name: Upload package
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ github.token }}
file: InvoiceShelf.zip
asset_name: InvoiceShelf.zip
tag: ${{ github.ref }}
overwrite: true
release_docker_build:
name: 🐳 Release Docker Build
if: github.event_name == 'release'
needs:
- tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_HUB_USERNAME }}/invoiceshelf
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/production/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
manual_docker_build:
name: 🐳 MANUAL Docker Build
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/production/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/invoiceshelf:${{ github.event.inputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
nightly_build:
name: 🌙 Nightly Docker Build
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
strategy:
matrix:
branch: [master, develop]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
fetch-depth: 2
- name: Check for recent changes
id: changes
run: |
# Check if there are commits in the last 24 hours
RECENT_COMMITS=$(git log --since="24 hours ago" --oneline | wc -l)
echo "recent_commits=$RECENT_COMMITS" >> $GITHUB_OUTPUT
if [ "$RECENT_COMMITS" -gt 0 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- name: Set up Docker Buildx
if: steps.changes.outputs.has_changes == 'true'
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: steps.changes.outputs.has_changes == 'true'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Set Docker tag
if: steps.changes.outputs.has_changes == 'true'
id: tag
run: |
if [ "${{ matrix.branch }}" = "master" ]; then
echo "tag=nightly" >> $GITHUB_OUTPUT
elif [ "${{ matrix.branch }}" = "develop" ]; then
echo "tag=alpha" >> $GITHUB_OUTPUT
fi
- name: Build and push Docker image
if: steps.changes.outputs.has_changes == 'true'
uses: docker/build-push-action@v5
with:
context: .
file: docker/production/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/invoiceshelf:${{ steps.tag.outputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: No changes detected
if: steps.changes.outputs.has_changes == 'false'
run: |
echo "No commits found in the last 24 hours for ${{ matrix.branch }} branch. Skipping build."

5
.gitignore vendored
View File

@@ -22,5 +22,6 @@ Homestead.yaml
.php-cs-fixer.cache
/storage/fonts*
package-lock.json
/.dev/docker-compose.yml
/.dev/docker-compose.yaml
/docker/development/docker-compose.yml
/docker/production/docker-compose.yml
/docker-compose.yaml

View File

@@ -26,10 +26,3 @@ FROM base AS development
RUN docker-php-serversideup-set-id www-data $UID:$GID
USER www-data
FROM base AS production
ENV AUTORUN_ENABLED=true
COPY --from=static_builder --chown=www-data:www-data /var/www/html/public /var/www/html/public
COPY --chown=www-data:www-data . /var/www/html
RUN composer install --prefer-dist
USER www-data

View File

@@ -2,28 +2,28 @@ services:
php-fpm:
container_name: invoiceshelf-dev-php
build:
context: ..
dockerfile: .dev/Dockerfile
context: ../../
dockerfile: docker/development/Dockerfile
args:
- UID=${USRID:-1000}
- GID=${GRPID:-1000}
target: development
volumes:
- ../:/var/www/html
- ../../:/var/www/html
networks:
- invoiceshelf-dev
nginx:
container_name: invoiceshelf-dev-nginx
build:
context: ..
dockerfile: .dev/nginx.Dockerfile
context: ../../
dockerfile: docker/development/nginx.Dockerfile
environment:
- "PHP_FPM_HOST=php-fpm:9000"
ports:
- '80:80'
volumes:
- ../:/var/www/html
- ../../:/var/www/html
networks:
invoiceshelf-dev:
aliases:
@@ -47,8 +47,8 @@ services:
adminer:
container_name: invoiceshelf-dev-adminer
build:
context: ./adminer
dockerfile: Dockerfile
context: ../../
dockerfile: docker/development/adminer/Dockerfile
environment:
ADMINER_PLUGINS: tables-filter
ADMINER_DESIGN: konya

View File

@@ -9,7 +9,7 @@ services:
- GID=${GRPID:-1000}
target: development
volumes:
- ../:/var/www/html
- ../../:/var/www/html
networks:
- invoiceshelf-dev
@@ -23,7 +23,7 @@ services:
ports:
- '80:80'
volumes:
- ../:/var/www/html
- ../../:/var/www/html
networks:
invoiceshelf-dev:
aliases:

View File

@@ -9,7 +9,7 @@ services:
- GID=${GRPID:-1000}
target: development
volumes:
- ../:/var/www/html
- ../../:/var/www/html
networks:
- invoiceshelf-dev
@@ -23,7 +23,7 @@ services:
ports:
- '80:80'
volumes:
- ../:/var/www/html
- ../../:/var/www/html
networks:
invoiceshelf-dev:
aliases:

View File

@@ -54,4 +54,3 @@ server {
}
}
EOF

View File

@@ -0,0 +1,41 @@
FROM --platform=$BUILDPLATFORM node:20 AS static_builder
WORKDIR /var/www/html
COPY . /var/www/html
RUN yarn && yarn build
FROM serversideup/php:8.3-fpm-nginx-alpine AS base
USER root
RUN install-php-extensions exif
RUN install-php-extensions pgsql
RUN install-php-extensions sqlite3
RUN install-php-extensions imagick
RUN install-php-extensions mbstring
RUN install-php-extensions gd
RUN install-php-extensions xml
RUN install-php-extensions zip
RUN install-php-extensions redis
RUN install-php-extensions bcmath
RUN install-php-extensions intl
RUN install-php-extensions curl
FROM base AS production
ENV AUTORUN_ENABLED=true
ENV PHP_OPCACHE_ENABLE=1
# Set `www-data` as the user to start FPM
USER root
RUN echo "" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf && \
echo "user = www-data" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf && \
echo "group = www-data" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf
# Revert back to www-data, non-root user
USER www-data
# Copy application files
COPY --from=static_builder --chown=www-data:www-data /var/www/html/public /var/www/html/public
COPY --chown=www-data:www-data . /var/www/html
RUN composer install --prefer-dist --no-dev --optimize-autoloader
# Copy entrypoint and inject script, and make sure they are executable
COPY --chmod=755 docker/production/inject.sh /inject.sh
COPY --chmod=755 docker/production/entrypoint.d/ /etc/entrypoint.d/

View File

@@ -0,0 +1,75 @@
#-------------------------------------------
# InvoiceShelf MySQL docker-compose variant
# Repo : https://github.com/InvoiceShelf/docker
#-------------------------------------------
services:
database:
container_name: invoiceshelf-mdb
image: mariadb:10
environment:
- MYSQL_DATABASE=invoiceshelf
- MYSQL_USER=invoiceshelf
- MYSQL_PASSWORD=somepass
- MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true
expose:
- 3306
volumes:
- mysql:/var/lib/mysql
networks:
- invoiceshelf
restart: unless-stopped
healthcheck:
test: ["CMD", "mariadb-admin" ,"ping", "-h", "localhost"]
timeout: 20s
retries: 10
webapp:
container_name: invoiceshelf-app
build:
context: ../../
dockerfile: docker/production/Dockerfile
ports:
- 90:8080
volumes:
- appdata:/var/www/html/storage/
networks:
- invoiceshelf
environment:
- CONTAINERIZED=true
- APP_NAME=Laravel
- APP_ENV=local
- APP_DEBUG=true
- APP_URL=http://localhost:90
- DB_CONNECTION=mysql
- DB_HOST=database
- DB_PORT=3306
- DB_DATABASE=invoiceshelf
- DB_USERNAME=invoiceshelf
- DB_PASSWORD=somepass
- DB_PASSWORD_FILE=
- CACHE_STORE=file
- SESSION_DRIVER=file
- SESSION_LIFETIME=120
- SESSION_ENCRYPT=false
- SESSION_PATH=/
- SESSION_DOMAIN=localhost
- SANCTUM_STATEFUL_DOMAINS=localhost:90
- STARTUP_DELAY=
#- MAIL_MAILER=smtp
#- MAIL_HOST=smtp.mailtrap.io
#- MAIL_PORT=2525
#- MAIL_USERNAME=null
#- MAIL_PASSWORD=null
#- MAIL_PASSWORD_FILE=<filename>
#- MAIL_ENCRYPTION=null
restart: unless-stopped
depends_on:
- database
networks:
invoiceshelf:
volumes:
mysql:
appdata:

View File

@@ -0,0 +1,51 @@
#!/bin/bash
set -e
# Read version information
version=$(head -n 1 /var/www/html/version.md)
echo "
-------------------------------------
InvoiceShelf Version: $version
-------------------------------------"
if [ -n "$STARTUP_DELAY" ]
then echo "**** Delaying startup ($STARTUP_DELAY seconds)... ****"
sleep $STARTUP_DELAY
fi
cd /var/www/html
cp .env.example .env
if [ "$DB_CONNECTION" = "sqlite" ] || [ -z "$DB_CONNECTION" ]; then
echo "**** Configure SQLite3 database ****"
if [ ! -n "$DB_DATABASE" ]; then
echo "**** DB_DATABASE not defined. Fall back to default /database/database.sqlite location ****"
DB_DATABASE='/var/www/html/database/database.sqlite'
fi
if [ ! -e "$DB_DATABASE" ]; then
echo "**** Specified sqlite database doesn't exist. Creating it ****"
echo "**** Please make sure your database is on a persistent volume ****"
sqlite3 "$DB_DATABASE" "VACUUM;"
fi
chown www-data:www-data "$DB_DATABASE"
fi
echo "**** Inject .env values ****" && \
/inject.sh
echo "**** Setting up artisan permissions ****"
chmod +x artisan
if ! grep -q "APP_KEY" /var/www/html/.env
then
echo "**** Creating empty APP_KEY variable ****"
echo "$(printf "APP_KEY=\n"; cat /var/www/html/.env)" > /var/www/html/.env
fi
if ! grep -q '^APP_KEY=[^[:space:]]' /var/www/html/.env; then
echo "**** Generating new APP_KEY variable ****"
./artisan key:generate -n
fi

110
docker/production/inject.sh Normal file
View File

@@ -0,0 +1,110 @@
#!/bin/bash
function replace_or_insert() {
# Voodoo magic: https://superuser.com/a/976712
grep -q "^${1}=" /var/www/html/.env && sed "s|^${1}=.*|${1}=${2}|" -i /var/www/html/.env || sed "$ a\\${1}=${2}" -i /var/www/html/.env
}
replace_or_insert "CONTAINERIZED" "true"
if [ "$APP_NAME" != '' ]; then
replace_or_insert "APP_NAME" "$APP_NAME"
fi
if [ "$APP_ENV" != '' ]; then
replace_or_insert "APP_ENV" "$APP_ENV"
fi
if [ "$APP_KEY" != '' ]; then
replace_or_insert "APP_KEY" "$APP_KEY"
fi
if [ "$APP_DEBUG" != '' ]; then
replace_or_insert "APP_DEBUG" "$APP_DEBUG"
fi
if [ "$APP_URL" != '' ]; then
replace_or_insert "APP_URL" "$APP_URL"
fi
if [ "$APP_DIR" != '' ]; then
replace_or_insert "APP_DIR" "$APP_DIR"
fi
if [ "$DB_CONNECTION" != '' ]; then
replace_or_insert "DB_CONNECTION" "$DB_CONNECTION"
fi
if [ "$DB_HOST" != '' ]; then
replace_or_insert "DB_HOST" "$DB_HOST"
fi
if [ "$DB_PORT" != '' ]; then
replace_or_insert "DB_PORT" "$DB_PORT"
fi
if [ "$DB_DATABASE" != '' ]; then
replace_or_insert "DB_DATABASE" "$DB_DATABASE"
fi
if [ "$DB_USERNAME" != '' ]; then
replace_or_insert "DB_USERNAME" "$DB_USERNAME"
fi
if [ "$DB_PASSWORD" != '' ]; then
replace_or_insert "DB_PASSWORD" "$DB_PASSWORD"
elif [ "$DB_PASSWORD_FILE" != '' ]; then
value=$(<$DB_PASSWORD_FILE)
replace_or_insert "DB_PASSWORD" "$value"
fi
if [ "$TIMEZONE" != '' ]; then
replace_or_insert "TIMEZONE" "$TIMEZONE"
fi
if [ "$CACHE_STORE" != '' ]; then
replace_or_insert "CACHE_STORE" "$CACHE_STORE"
fi
if [ "$CACHE_DRIVER" != '' ]; then
replace_or_insert "CACHE_STORE" "$CACHE_DRIVER" # deprecated (will be removed later)
fi
if [ "$SESSION_DRIVER" != '' ]; then
replace_or_insert "SESSION_DRIVER" "$SESSION_DRIVER"
fi
if [ "$SESSION_LIFETIME" != '' ]; then
replace_or_insert "SESSION_LIFETIME" "$SESSION_LIFETIME"
fi
if [ "$QUEUE_CONNECTION" != '' ]; then
replace_or_insert "QUEUE_CONNECTION" "$QUEUE_CONNECTION"
fi
if [ "$BROADCAST_CONNECTION" != '' ]; then
replace_or_insert "BROADCAST_CONNECTION" "$BROADCAST_CONNECTION"
fi
if [ "$MAIL_DRIVER" != '' ]; then
replace_or_insert "MAIL_MAILER" "$MAIL_DRIVER"
fi
if [ "$MAIL_MAILER" != '' ]; then
replace_or_insert "MAIL_MAILER" "$MAIL_MAILER"
fi
if [ "$MAIL_HOST" != '' ]; then
replace_or_insert "MAIL_HOST" "$MAIL_HOST"
fi
if [ "$MAIL_PORT" != '' ]; then
replace_or_insert "MAIL_PORT" "$MAIL_PORT"
fi
if [ "$MAIL_USERNAME" != '' ]; then
replace_or_insert "MAIL_USERNAME" "$MAIL_USERNAME"
fi
if [ "$MAIL_PASSWORD" != '' ]; then
replace_or_insert "MAIL_PASSWORD" "$MAIL_PASSWORD"
elif [ "$MAIL_PASSWORD_FILE" != '' ]; then
value=$(<$MAIL_PASSWORD_FILE)
replace_or_insert "MAIL_PASSWORD" "$value"
fi
if [ "$MAIL_SCHEME" != '' ]; then
replace_or_insert "MAIL_SCHEME" "$MAIL_SCHEME"
fi
if [ "$MAIL_FROM_NAME" != '' ]; then
replace_or_insert "MAIL_FROM_NAME" "$MAIL_FROM_NAME"
fi
if [ "$MAIL_FROM_ADDRESS" != '' ]; then
replace_or_insert "MAIL_FROM_ADDRESS" "$MAIL_FROM_ADDRESS"
fi
if [ "$TRUSTED_PROXIES" != '' ]; then
replace_or_insert "TRUSTED_PROXIES" "$TRUSTED_PROXIES"
fi
if [ "$SANCTUM_STATEFUL_DOMAINS" != '' ]; then
replace_or_insert "SANCTUM_STATEFUL_DOMAINS" "$SANCTUM_STATEFUL_DOMAINS"
fi
if [ "$SESSION_DOMAIN" != '' ]; then
replace_or_insert "SESSION_DOMAIN" "$SESSION_DOMAIN"
fi