🚢 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

View File

@@ -0,0 +1,28 @@
FROM --platform=$BUILDPLATFORM node AS static_builder
WORKDIR /var/www/html
COPY . /var/www/html
RUN yarn && yarn build
FROM serversideup/php:8-fpm-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 development
ARG UID
ARG GID
USER root
RUN docker-php-serversideup-set-id www-data $UID:$GID
USER www-data

View File

@@ -0,0 +1,166 @@
# InvoiceShelf Development Environment
This is dockerized development environment that allows developers to easily get started to develop InvoiceShelf.
This development environment is **NOT MEANT TO BE USED IN PRODUCTION** and is preconfigured with all the needed tools that InvoiceShelf requires for development purposes. It works on Windows, Linux and MacOS.
For production grade docker image, please refer to [InvoiceShelf/docker](https://github.com/InvoiceShelf/docker) and [InvoiceShelf on DockerHub](https://hub.docker.com/r/invoiceshelf/invoiceshelf).
## How to set up
### 1. Hosts configuration
We use `invoiceshelf.test` domain for local development within this environment and you need to adhere to it.
For that purpose you need to edit your OS hosts file or DNS server and add the following line to make the local domain name available on your system.
```
127.0.0.1 invoiceshelf.test
```
#### 1.1. Windows
The hosts file on Windows is located at `C:\Windows\system32\drivers\etc\hosts`.
You need to launch Notepad as administrator, open the file through **File > Open**, add the line from above and save the file.
#### 1.2. Linux/MacOS
The hosts file on Linux and Mac is located at `/etc/hosts`.
You need to open the file using your favorite editor as sudo/root, add the line from above and save the file.
### 2. FileSystem configuration (Linux)
If you are using **Linux**, you need to make sure that **USRID** and **GRPID** environment variables are set and matching your current session user ids. Those two variables are required to set up the filesystem permissions correctly on Linux.
You can run it one time, every time before starting as follows:
```
export USRID=$(id -u) && export GRPID=$(id -g)
```
or you can append this to your .zshrc/.bashrc by running this command in your terminal:
```
grep -qxF 'export USRID=$(id -u) GRPID=$(id -g)' ~/.${SHELL##*/}rc || echo 'export USRID=$(id -u) GRPID=$(id -g)' >> ~/.${SHELL##*/}rc
```
this will append the `export` line to your rc file and run it on each terminal session.
### 3. Clone the project
Clone the InvoiceShelf project directly from InvoiceShelf git or your forked repository:
```bash
git clone git@github.com:InvoiceShelf/InvoiceShelf.git
```
## Development Workflow
We bundled separate docker-compose.yml file for each database: MySQL, PostgresSQL and SQLite, you can use any of those to spin up your development environment.
| Database | Compose File |
|---------|---------------------------|
| SQLite3 | docker-compose.sqlite.yml |
| MariaDB | docker-compose.mysql.yml |
| PostgresSQL | dpcler-compose.pgsql.yml |
### 1. Spinning Up
To **spin up** the environment, run docker compose as follows:
**Important**: If you are on **Linux** and didn't add the `export` line to your .zshrc/.bashrc file, you need to repeat `step 2` before spinning up, otherwise you will face permissions issues.
```
docker compose -f .dev/docker-compose.mysql.yml up --build
```
### 2. Spinning Down
To **spin down** the environment, run docker compose as follows:
```
docker compose -f .dev/docker-compose.mysql.yml down
```
### 3. Working with binaries
To correctly run `composer`, `npm`, `artisan`, `pint`, `pest` or other binaries within this project, you must ssh into the container as follows:
```
docker exec -it invoiceshelf /bin/bash
```
In the `/var/www/html` directory you can find the application root and run the commands from there.
## What is included
### 1. Web Server
This dockerized environment uses PHP-FPM and NGINX together to serve the website `invoiceshelf.test`
Both NGINX and PHP-FPM are configured with optimal settings for development. Please don't use this in production.
**URL**: http://invoiceshelf.test/
### 2. Databases
This dockerized environment comes with support for all three databases that InvoiceShelf suppots: MySQL, PostgreSQL and SQLite.
The setup parameters/credentials for each of the supported databases are as follows.
| | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
| **DB_USER** | invoiceshelf | invoiceshelf | Not applicable |
| **DB_PASS** | invoiceshelf | invoiceshelf | Not applicable |
| **DB_NAME** | invoiceshelf | invoiceshelf | /var/www/html/database/database.sqlite |
| **DB_HOST** | db-mysql | db-pgsql | Not applicable |
| **DB_PORT** | 3036 | 5432 | Not applicable |
**Note:** The only required field for SQLite is **DB_NAME**.
### 3. Adminer
Adminer is UI tool for viewing the database contents and executing queries.
It supports MySQL, PostgreSQL, SQLite.
**URL**: http://invoiceshelf.test:8080
#### MySQL/PostgresSQL
To log into the MySQL or PostgresSQL, use the database information specified in the above section (2. Databases)
#### SQLite
To log into the SQLite, use the following credentials:
| KEY | VALUE |
|--------------|---------------------------|
| **USERNAME** | admin |
| **PASSWORD** | admin |
| **DATABASE** | /database/database.sqlite |
### 4. Mailpit (fake mail)
To utilize Mailpit, use the following credentials:
| KEY | VALUE |
|---------------------|-------------|
| **MAIL DRIVER** | smtp |
| **MAIL HOST** | mail |
| **MAIL PORT** | 1025 |
| **MAIL ENCRYPTION** | none |
| **MAIL USER** | leave empty |
| **MAIL PASS** | leave empty |
| **FROM MAIL ADDR** | your choice |
| **FROM MAIL NAME** | your choice |
**URL**: http://invoiceshelf.test:8025
---
If you have any questions, feel free to open issue.

View File

@@ -0,0 +1,8 @@
FROM adminer:latest
USER root
USER adminer
CMD [ "php", "-S", "[::]:8080", "-t", "/var/www/html" ]
EXPOSE 8080

View File

@@ -0,0 +1,79 @@
services:
php-fpm:
container_name: invoiceshelf-dev-php
build:
context: ../../
dockerfile: docker/development/Dockerfile
args:
- UID=${USRID:-1000}
- GID=${GRPID:-1000}
target: development
volumes:
- ../../:/var/www/html
networks:
- invoiceshelf-dev
nginx:
container_name: invoiceshelf-dev-nginx
build:
context: ../../
dockerfile: docker/development/nginx.Dockerfile
environment:
- "PHP_FPM_HOST=php-fpm:9000"
ports:
- '80:80'
volumes:
- ../../:/var/www/html
networks:
invoiceshelf-dev:
aliases:
- invoiceshelf.test
db:
image: mariadb:10.9
container_name: invoiceshelf-dev-mysql
environment:
MYSQL_ROOT_PASSWORD: invoiceshelf
MYSQL_DATABASE: invoiceshelf
MYSQL_USER: invoiceshelf
MYSQL_PASSWORD: invoiceshelf
ports:
- "3306:3306"
volumes:
- invoiceshelf-dev-mysql:/var/lib/mysql
networks:
- invoiceshelf-dev
adminer:
container_name: invoiceshelf-dev-adminer
build:
context: ../../
dockerfile: docker/development/adminer/Dockerfile
environment:
ADMINER_PLUGINS: tables-filter
ADMINER_DESIGN: konya
ports:
- '8080:8080'
networks:
- invoiceshelf-dev
mail:
container_name: invoiceshelf-dev-mailpit
image: axllent/mailpit:latest
restart: always
ports:
- 1025:1025
- 8025:8025
networks:
- invoiceshelf-dev
pdf:
image: gotenberg/gotenberg:8
networks:
- invoiceshelf-dev
networks:
invoiceshelf-dev:
volumes:
invoiceshelf-dev-mysql:

View File

@@ -0,0 +1,78 @@
services:
php-fpm:
container_name: invoiceshelf-dev-php
build:
context: ..
dockerfile: .dev/Dockerfile
args:
- UID=${USRID:-1000}
- GID=${GRPID:-1000}
target: development
volumes:
- ../../:/var/www/html
networks:
- invoiceshelf-dev
nginx:
container_name: invoiceshelf-dev-nginx
build:
context: ..
dockerfile: .dev/nginx.Dockerfile
environment:
- "PHP_FPM_HOST=php-fpm:9000"
ports:
- '80:80'
volumes:
- ../../:/var/www/html
networks:
invoiceshelf-dev:
aliases:
- invoiceshelf.test
db:
image: postgres:15
container_name: invoiceshelf-dev-pgsql
environment:
- POSTGRES_PASSWORD=invoiceshelf
- POSTGRES_USER=invoiceshelf
- POSTGRES_DB=invoiceshelf
ports:
- 5432:5432
volumes:
- invoiceshelf-dev-pgsql:/var/lib/postgresql/data
networks:
- invoiceshelf-dev
adminer:
container_name: invoiceshelf-dev-adminer
build:
context: ./adminer
dockerfile: Dockerfile
environment:
ADMINER_PLUGINS: tables-filter
ADMINER_DESIGN: konya
ports:
- '8080:8080'
networks:
- invoiceshelf-dev
mail:
container_name: invoiceshelf-dev-mailpit
image: axllent/mailpit:latest
restart: always
ports:
- 1025:1025
- 8025:8025
networks:
- invoiceshelf-dev
pdf:
image: gotenberg/gotenberg:8
networks:
- invoiceshelf-dev
networks:
invoiceshelf-dev:
volumes:
invoiceshelf-dev-pgsql:

View File

@@ -0,0 +1,62 @@
services:
php-fpm:
container_name: invoiceshelf-dev-php
build:
context: ..
dockerfile: .dev/Dockerfile
args:
- UID=${USRID:-1000}
- GID=${GRPID:-1000}
target: development
volumes:
- ../../:/var/www/html
networks:
- invoiceshelf-dev
nginx:
container_name: invoiceshelf-dev-nginx
build:
context: ..
dockerfile: .dev/nginx.Dockerfile
environment:
- "PHP_FPM_HOST=php-fpm:9000"
ports:
- '80:80'
volumes:
- ../../:/var/www/html
networks:
invoiceshelf-dev:
aliases:
- invoiceshelf.test
adminer:
container_name: invoiceshelf-dev-adminer
build:
context: ./adminer
dockerfile: Dockerfile
environment:
ADMINER_PLUGINS: tables-filter
ADMINER_DESIGN: konya
volumes:
- ../database:/database
ports:
- '8080:8080'
networks:
- invoiceshelf-dev
mail:
container_name: invoiceshelf-dev-mailpit
image: axllent/mailpit:latest
ports:
- '1025:1025'
- '8025:8025'
networks:
- invoiceshelf-dev
pdf:
image: gotenberg/gotenberg:8
networks:
- invoiceshelf-dev
networks:
invoiceshelf-dev:

View File

@@ -0,0 +1,56 @@
FROM --platform=$BUILDPLATFORM node AS static_builder
WORKDIR /var/www/html
COPY . /var/www/html
RUN yarn && yarn build
FROM nginx AS production
ENV PHP_FPM_HOST="php-fpm:9000"
COPY --chown=www-data:www-data . /var/www/html
COPY --from=static_builder --chown=www-data:www-data /var/www/html/public /var/www/html/public
# Map the PHP-FPM host from the PHP_FPM_HOST environment variable to an nging variable
RUN mkdir /etc/nginx/templates && cat <<EOF > /etc/nginx/templates/20-invoiceshelf.conf.template
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html/public;
# Set allowed "index" files
index index.html index.htm index.php;
server_name _;
charset utf-8;
# Set max upload to 2048M
client_max_body_size 2048M;
# Healthchecks: Set /healthcheck to be the healthcheck URL
location /healthcheck {
access_log off;
# set max 5 seconds for healthcheck
fastcgi_read_timeout 5s;
include fastcgi_params;
fastcgi_param SCRIPT_NAME /healthcheck;
fastcgi_param SCRIPT_FILENAME /healthcheck;
fastcgi_pass \${PHP_FPM_HOST};
}
# Have NGINX try searching for PHP files as well
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
# Pass "*.php" files to PHP-FPM
location ~ \.php\$ {
fastcgi_pass \${PHP_FPM_HOST};
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffers 8 8k;
fastcgi_buffer_size 8k;
}
}
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