Skip to content
Closed
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
90 changes: 54 additions & 36 deletions .github/DOCKER.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
DOCKER.md

# Docker Setup for InvoicePlane V2

This guide explains how to run InvoicePlane V2 using Docker.
This guide explains how to run InvoicePlane V2 using Docker — both the web
stack and the standalone CLI image for running tests and artisan commands.

---

Expand All @@ -16,66 +15,85 @@ This guide explains how to run InvoicePlane V2 using Docker.
## Quick Start

```bash
git clone https://github.com/InvoicePlane/InvoicePlane.git
cd InvoicePlane
git clone https://github.com/InvoicePlane/InvoicePlane-v2.git
cd InvoicePlane-v2

cp .env.example .env
composer install
php artisan key:generate
php artisan migrate --seed

# Install dependencies and bootstrap the app through the CLI container —
# no PHP required on the host:
docker compose run --rm cli composer install
docker compose run --rm cli php artisan key:generate
docker compose up -d
docker compose run --rm cli php artisan migrate --seed
```

Visit: http://localhost/ivpl
Visit: http://localhost:8080 (override the port with `APP_PORT` in `.env`).

---

Useful Commands
## Services

Action Command
| Service | Image | Purpose |
|---|---|---|
| `web` | `docker-resources/apache` (httpd 2.4 alpine) | Serves `public/`, proxies PHP to `app` |
| `app` | `docker-resources/php-fpm` (PHP 8.4 fpm alpine) | Laravel application (FPM) |
| `cli` | `docker-resources/php-cli` (PHP 8.4 cli alpine) | One-off runner for tests / artisan / composer — profile `tools`, never auto-started |
| `db` | `mariadb` | Database (port 3306) |
| `mailcatcher` | `sj26/mailcatcher` | Catches outgoing mail — UI on port 1080 |

Start services docker compose up -d
Stop services docker compose down
View logs docker compose logs -f
Run artisan docker compose exec app php artisan
Rebuild containers docker compose build --no-cache
Both PHP images ship the full extension set the app needs: `intl`, `gd`,
`pdo_mysql`, `bcmath`, `zip`, `exif`, `soap`, `redis`. The CLI image also has
Composer, a 1G memory limit for the test suite, and bundled `pdo_sqlite`
(the suite runs on an in-memory sqlite database — no db service needed for
tests).

---

Services

App container: Laravel application

Database: MariaDB (latest)

Mail: MailCatcher (port 1080)

Queue: Redis (optional)
## Running the test suite

---
```bash
docker compose run --rm cli vendor/bin/phpunit --exclude-group failing,troubleshooting
```

Customize Docker
`APP_ENV=testing` is the `cli` service default, so `.env.testing`
(sqlite `:memory:`) is picked up automatically. See `RUNNING_TESTS.md` for
filters, groups, and suites.

Change database port in docker-compose.yml
### File ownership on Linux

Override PHP version via Dockerfile
The CLI image creates its user with uid/gid `1000`. If your host user
differs, rebuild with your ids so files written into the mounted repo
(vendor/, storage/, compiled views) stay owned by you:

Add volumes for local persistence if needed
```bash
docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) cli
```

---

Troubleshooting
## Useful Commands

Port already in use: Adjust ports in docker-compose.yml
| Action | Command |
|---|---|
| Start services | `docker compose up -d` |
| Stop services | `docker compose down` |
| View logs | `docker compose logs -f` |
| Run artisan | `docker compose run --rm cli php artisan <command>` |
| Run composer | `docker compose run --rm cli composer <command>` |
| Rebuild containers | `docker compose build --no-cache` |

Permission issues: Ensure Docker has access to your project folder
---

Missing .env config: Re-run cp .env.example .env and adjust
## Troubleshooting

---
- **Port already in use**: set `APP_PORT` in `.env` (web) or adjust ports in `docker-compose.yml`
- **Permission issues**: rebuild the `cli` image with your `UID`/`GID` (see above)
- **Missing .env config**: re-run `cp .env.example .env` and adjust
- **Tests fail with `could not find driver` or missing `intl`**: you are running on host PHP — use the `cli` container instead

---

What's Next?
## What's Next?

Visit CHECKLIST.md if contributing
52 changes: 52 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,53 @@ volumes:
driver: "local"

services:
app:
container_name: 'ivplflmnt_app'
build:
context: ./docker-resources/php-fpm
restart: unless-stopped
tty: true
volumes:
- .:/var/www/html
depends_on:
- db
networks:
- laravel

web:
container_name: 'ivplflmnt_web'
build:
context: ./docker-resources/apache
restart: unless-stopped
tty: true
ports:
- "${APP_PORT:-8080}:80"
volumes:
- .:/usr/local/apache2/htdocs
depends_on:
- app
networks:
- laravel

# One-off command runner for tests, artisan, and composer — not started
# by default (profile "tools"). Examples:
# docker compose run --rm cli composer install
# docker compose run --rm cli php artisan migrate --seed
# docker compose run --rm cli vendor/bin/phpunit --exclude-group failing,troubleshooting
cli:
container_name: 'ivplflmnt_cli'
build:
context: ./docker-resources/php-cli
profiles:
- tools
tty: true
environment:
APP_ENV: "${APP_ENV:-testing}"
volumes:
- .:/var/www/html
networks:
- laravel

db:
container_name: 'ivplflmnt_db'
image: mariadb
Expand All @@ -16,6 +63,8 @@ services:
MARIADB_USER: "${DB_USERNAME}"
MARIADB_PASSWORD: "${DB_PASSWORD}"
TZ: "Europe/London"
networks:
- laravel

mailcatcher:
container_name: 'ivplflmnt_mailcatcher'
Expand All @@ -25,6 +74,9 @@ services:
ports:
- "25:1025"
- "1080:1080"
networks:
- laravel

# phpmyadmin:
# container_name: 'ivplflmnt_phpmyadmin'
# image: phpmyadmin/phpmyadmin
Expand Down
14 changes: 14 additions & 0 deletions docker-resources/apache/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM httpd:2.4-alpine

# Enable required Apache modules for PHP-FPM proxying
RUN sed -i \
-e 's/^#\(LoadModule proxy_module modules\/mod_proxy.so\)/\1/' \
-e 's/^#\(LoadModule proxy_fcgi_module modules\/mod_proxy_fcgi.so\)/\1/' \
-e 's/^#\(LoadModule rewrite_module modules\/mod_rewrite.so\)/\1/' \
/usr/local/apache2/conf/httpd.conf

COPY config/invoiceplane-vhost.conf /usr/local/apache2/conf/extra/invoiceplane-vhost.conf

RUN echo "Include conf/extra/invoiceplane-vhost.conf" >> /usr/local/apache2/conf/httpd.conf

EXPOSE 80
21 changes: 21 additions & 0 deletions docker-resources/apache/config/invoiceplane-vhost.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<VirtualHost *:80>
ServerName localhost
DocumentRoot "/usr/local/apache2/htdocs/public"

<Directory "/usr/local/apache2/htdocs/public">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>

# ProxyPass for PHP-FPM with correct document root
<FilesMatch \.php$>
SetHandler "proxy:fcgi://app:9000/var/www/html/public"
</FilesMatch>

# Fallback for PATH_INFO
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://app:9000/var/www/html/public/$1

ErrorLog /usr/local/apache2/logs/invoiceplane_error.log
CustomLog /usr/local/apache2/logs/invoiceplane_access.log combined
</VirtualHost>
18 changes: 18 additions & 0 deletions docker-resources/node/scripts/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh

# Copy original vite.config.js to Docker-specific version
mkdir -p /app/.docker
cp /app/vite.config.js /app/.docker/vite.config.docker.js

# Update resource paths to absolute paths
sed -i "s|'resources/css/app.css'|'/app/resources/css/app.css'|g" /app/.docker/vite.config.docker.js
sed -i "s|'resources/js/app.js'|'/app/resources/js/app.js'|g" /app/.docker/vite.config.docker.js

# Add Docker-specific server configuration if not already present
if ! grep -q "server:" /app/.docker/vite.config.docker.js; then
# Insert server config before the closing }); of defineConfig
sed -i '/^});$/i\ server: {\n host: '\''0.0.0.0'\'',\n port: 5173,\n hmr: {\n host: '\''localhost'\'',\n },\n },' /app/.docker/vite.config.docker.js
fi

# Install dependencies and start Vite with Docker config
npm install && npm run dev -- --config .docker/vite.config.docker.js
Comment on lines +1 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add set -e to fail fast on copy/sed errors.

Without set -e, if cp or sed fails (e.g., vite.config.js missing), the script continues to npm install && npm run dev, which fails with a confusing "config file not found" error instead of surfacing the original failure.

Proposed fix
 #!/bin/sh
+set -e
+
 # Copy original vite.config.js to Docker-specific version
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/bin/sh
# Copy original vite.config.js to Docker-specific version
mkdir -p /app/.docker
cp /app/vite.config.js /app/.docker/vite.config.docker.js
# Update resource paths to absolute paths
sed -i "s|'resources/css/app.css'|'/app/resources/css/app.css'|g" /app/.docker/vite.config.docker.js
sed -i "s|'resources/js/app.js'|'/app/resources/js/app.js'|g" /app/.docker/vite.config.docker.js
# Add Docker-specific server configuration if not already present
if ! grep -q "server:" /app/.docker/vite.config.docker.js; then
# Insert server config before the closing }); of defineConfig
sed -i '/^});$/i\ server: {\n host: '\''0.0.0.0'\'',\n port: 5173,\n hmr: {\n host: '\''localhost'\'',\n },\n },' /app/.docker/vite.config.docker.js
fi
# Install dependencies and start Vite with Docker config
npm install && npm run dev -- --config .docker/vite.config.docker.js
#!/bin/sh
set -e
# Copy original vite.config.js to Docker-specific version
mkdir -p /app/.docker
cp /app/vite.config.js /app/.docker/vite.config.docker.js
# Update resource paths to absolute paths
sed -i "s|'resources/css/app.css'|'/app/resources/css/app.css'|g" /app/.docker/vite.config.docker.js
sed -i "s|'resources/js/app.js'|'/app/resources/js/app.js'|g" /app/.docker/vite.config.docker.js
# Add Docker-specific server configuration if not already present
if ! grep -q "server:" /app/.docker/vite.config.docker.js; then
# Insert server config before the closing }); of defineConfig
sed -i '/^});$/i\ server: {\n host: '\''0.0.0.0'\'',\n port: 5173,\n hmr: {\n host: '\''localhost'\'',\n },\n },' /app/.docker/vite.config.docker.js
fi
# Install dependencies and start Vite with Docker config
npm install && npm run dev -- --config .docker/vite.config.docker.js
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-resources/node/scripts/entrypoint.sh` around lines 1 - 18, Add `set
-e` immediately after the shebang in the entrypoint script so failures from
`mkdir`, `cp`, or either `sed` command terminate execution before reaching the
npm install and Vite startup command.

67 changes: 67 additions & 0 deletions docker-resources/php-cli/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
FROM php:8.4-cli-alpine

# Match the host user so files created in mounted volumes (vendor/,
# storage/, compiled views) keep sane ownership. Override at build time:
# docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) cli
ARG UID=1000
ARG GID=1000

RUN addgroup -g ${GID} dockeruser \
&& adduser -D -s /bin/bash -u ${UID} -G dockeruser dockeruser

# Install build dependencies (temporary)
RUN apk add --no-cache --virtual .build-deps \
autoconf \
g++ \
make \
pkgconf \
zstd-dev \
# Install runtime dependencies (permanent)
&& apk add --no-cache \
bash \
git \
curl \
zip \
unzip \
icu-dev \
libxml2-dev \
oniguruma-dev \
libzip-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
zstd \
# Configure and install PHP extensions (pdo_sqlite ships with the base
# image — the test suite runs on an in-memory sqlite database)
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo \
pdo_mysql \
mbstring \
exif \
pcntl \
bcmath \
gd \
zip \
intl \
xml \
soap \
opcache \
# Install PECL extensions
&& pecl install redis \
&& docker-php-ext-enable redis \
# Remove only build dependencies
&& apk del .build-deps \
&& rm -rf /var/cache/apk/*

# PHPUnit needs more than the 128M default on the full suite
RUN echo 'memory_limit=1G' > /usr/local/etc/php/conf.d/memory-limit.ini

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

USER dockeruser

WORKDIR /var/www/html

CMD ["php", "-a"]
58 changes: 58 additions & 0 deletions docker-resources/php-fpm/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
FROM php:8.4-fpm-alpine

RUN adduser -D -s /bin/bash dockeruser

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

FPM container lacks configurable UID/GID.

The CLI Dockerfile accepts ARG UID / ARG GID so the container user can match the host user, but the FPM Dockerfile hardcodes adduser -D -s /bin/bash dockeruser (default UID 1000). The FPM process writes to storage/ and bootstrap/cache/ on the mounted volume; if the host user's UID differs, those files will be owned by UID 1000 and may be unwritable or unmanageable by the host user. Apply the same ARG UID / ARG GID pattern here, and pass build args from the app service in docker-compose.yml.

Proposed fix
+ARG UID=1000
+ARG GID=1000
+
+RUN addgroup -g ${GID} dockeruser \
+    && adduser -D -s /bin/bash -u ${UID} -G dockeruser dockeruser
-RUN adduser -D -s /bin/bash dockeruser

And in docker-compose.yml:

     app:
         container_name: 'ivplflmnt_app'
         build:
             context: ./docker-resources/php-fpm
+            args:
+                UID: ${UID:-1000}
+                GID: ${GID:-1000}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN adduser -D -s /bin/bash dockeruser
ARG UID=1000
ARG GID=1000
RUN addgroup -g ${GID} dockeruser \
&& adduser -D -s /bin/bash -u ${UID} -G dockeruser dockeruser
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-resources/php-fpm/Dockerfile` at line 3, Update the PHP-FPM Dockerfile
user setup to declare and use configurable UID and GID build arguments, matching
the existing CLI Dockerfile pattern instead of hardcoding dockeruser’s IDs.
Update the app service in docker-compose.yml to pass the host-aligned UID and
GID build arguments through to the FPM image.


# Install build dependencies (temporary)
RUN apk add --no-cache --virtual .build-deps \
autoconf \
g++ \
make \
pkgconf \
zstd-dev \
# Install runtime dependencies (permanent)
&& apk add --no-cache \
bash \
git \
curl \
zip \
unzip \
icu-dev \
libxml2-dev \
oniguruma-dev \
libzip-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
zstd \
# Configure and install PHP extensions
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo \
pdo_mysql \
mbstring \
exif \
pcntl \
bcmath \
gd \
zip \
intl \
xml \
soap \
opcache \
# Install PECL extensions
&& pecl install redis \
&& docker-php-ext-enable redis \
# Remove only build dependencies
&& apk del .build-deps \
&& rm -rf /var/cache/apk/*

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

USER dockeruser

WORKDIR /var/www/html

EXPOSE 9000

CMD ["php-fpm"]
Loading