Skip to content
Open
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
96 changes: 96 additions & 0 deletions docker/Dockerfile.churchcrm-frankenphp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# FrankenPHP Dockerfile for ChurchCRM
#
# FrankenPHP bundles Caddy and PHP in a single binary, so no separate
# web server container is needed. Use this image with the Caddyfile in
# docker/frankenphp/Caddyfile.
#
# Build:
# docker build -f Dockerfile.churchcrm-frankenphp -t churchcrm/crm:frankenphp .
#
# Required PHP extensions for ChurchCRM:
# bcmath, curl, exif, gd, gettext, iconv, intl, mbstring, mysqli,
# opcache, pdo_mysql, sodium, xml, zip

FROM dunglas/frankenphp:1-php8.4 AS base
LABEL maintainer="ChurchCRM"

# Expose HTTP (HTTPS/HTTP2 can be enabled via Caddyfile)
EXPOSE 80

# Install system dependencies required by PHP extensions
RUN apt-get update && \
apt-get install -y \
gettext \
locales \
locales-all \
libcurl4-openssl-dev \
libfreetype6-dev \
libicu-dev \
libjpeg-dev \
libonig-dev \
libpng-dev \
libxml2-dev \
libzip-dev \
&& rm -rf /var/lib/apt/lists/*

# Install PHP extensions using the install-php-extensions helper bundled
# with the FrankenPHP image (handles all build/configure steps automatically)
RUN install-php-extensions \
bcmath \
curl \
exif \
gd \
gettext \
iconv \
intl \
mbstring \
mysqli \
opcache \
pdo_mysql \
sodium \
xml \
zip

# Configure PHP settings
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
sed -i 's/^upload_max_filesize.*$/upload_max_filesize = 2G/g' $PHP_INI_DIR/php.ini && \
sed -i 's/^post_max_size.*$/post_max_size = 2G/g' $PHP_INI_DIR/php.ini && \
sed -i 's/^memory_limit.*$/memory_limit = 256M/g' $PHP_INI_DIR/php.ini && \
sed -i 's/^max_execution_time.*$/max_execution_time = 120/g' $PHP_INI_DIR/php.ini

# Copy the Caddyfile that routes ChurchCRM's Slim sub-applications correctly
COPY ./frankenphp/Caddyfile /etc/caddy/Caddyfile


# Production stage - minimal runtime
FROM base AS prod


# Dev stage - includes build tools for local development inside the container
FROM base AS dev

# Install development tools
RUN apt-get update && \
apt-get install -y \
git \
make \
python3 \
unzip \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install Xdebug for step-debugging
RUN install-php-extensions xdebug

# Install Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
rm composer-setup.php

# Install NVM + Node.js (needed to run npm run deploy inside the container)
RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh -o /opt/node-install.sh && \
chmod a+x /opt/node-install.sh && \
/opt/node-install.sh && \
rm /opt/node-install.sh

RUN /bin/bash -c "source /root/.nvm/nvm.sh && nvm install 24 && npm install -g node-gyp"
76 changes: 75 additions & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This directory contains two types of Docker configurations:
|---------------|----------|
| `docker-compose.yaml` | Development and testing (Apache + PHP). Used by the automated test suite and local development. |
| `docker-compose.nginx.yaml` | Self-hosted or production reference (nginx + PHP-FPM). Starting point for your own deployment. |
| `docker-compose.frankenphp.yaml` | Self-hosted or production reference (FrankenPHP). Simpler single-container alternative to nginx + PHP-FPM. |

---

Expand Down Expand Up @@ -199,4 +200,77 @@ Visit `http://localhost/` — you will see the setup wizard on first run.
`bcmath`, `curl`, `exif`, `gd`, `gettext`, `iconv`, `intl`, `mbstring`, `mysqli`,
`opcache`, `pdo_mysql`, `sodium`, `xml`, `zip`

The `Dockerfile.churchcrm-fpm-php8` installs all of these.
The `Dockerfile.churchcrm-fpm-php8` installs all of these.

---

### Self-Hosted / Production (FrankenPHP)

The `docker-compose.frankenphp.yaml` and `frankenphp/Caddyfile` files provide a
reference configuration for deploying ChurchCRM with **FrankenPHP** — an
all-in-one server that bundles Caddy and PHP in a single binary and container.
This results in a simpler two-service stack compared to nginx + PHP-FPM.

### Why FrankenPHP needs explicit routing

The same routing requirement applies as for nginx: ChurchCRM is structured as
multiple independent **Slim 4 PHP applications**, each in its own subdirectory
with its own `index.php` entry point.

| URL prefix | Entry point |
|------------|-------------|
| `/session/` | `session/index.php` — login, logout, 2FA |
| `/api/` | `api/index.php` — REST API |
| `/v2/` | `v2/index.php` — modern MVC pages |
| `/admin/` | `admin/index.php` — admin panel |
| `/finance/` | `finance/index.php` — finance module |
| `/kiosk/` | `kiosk/index.php` — check-in kiosk |
| `/plugins/` | `plugins/index.php` — plugin system |
| `/external/` | `external/index.php` — public integrations |
| `/setup/` | `setup/index.php` — first-run wizard |
| `/` | `index.php` — legacy PHP pages |

With **Apache**, each subdirectory's `.htaccess` file automatically routes
requests to the correct entry point.

With **FrankenPHP (Caddy)**, you must explicitly map each URL prefix to its
entry point in the `Caddyfile`. **Routing all requests to the root `index.php`**
(a common mistake) causes an infinite redirect loop because unauthenticated
users are redirected to `/session/begin`, but that path also goes to
`index.php`, which redirects again.

### Quick start

```bash
# From the docker/ directory:
docker compose -f docker-compose.frankenphp.yaml up -d
```

Visit `http://localhost/` — you will see the setup wizard on first run.

### Files

| File | Description |
|------|-------------|
| `docker-compose.frankenphp.yaml` | Example Compose file (FrankenPHP + MariaDB) |
| `frankenphp/Caddyfile` | Caddy server block with correct per-subdirectory routing |
| `Dockerfile.churchcrm-frankenphp` | FrankenPHP image with all required PHP extensions |

### Customising the Caddyfile

1. Copy `frankenphp/Caddyfile` to your deployment.
2. Update `root` to the path where ChurchCRM's `src/` contents are served from.
3. For a **subdirectory install** (e.g. `http://example.com/churchcrm/`):
- Set `$sRootPath = '/churchcrm'` in `Include/Config.php`.
- Prefix all `handle` paths in the Caddyfile with `/churchcrm`.
- See the commented example at the bottom of `frankenphp/Caddyfile`.
4. To enable **automatic HTTPS**, replace `:80` with your domain name (Caddy
provisions a Let's Encrypt certificate automatically).

### Required PHP extensions

`bcmath`, `curl`, `exif`, `gd`, `gettext`, `iconv`, `intl`, `mbstring`, `mysqli`,
`opcache`, `pdo_mysql`, `sodium`, `xml`, `zip`

The `Dockerfile.churchcrm-frankenphp` installs all of these via the
`install-php-extensions` helper bundled with the FrankenPHP base image.
64 changes: 64 additions & 0 deletions docker/docker-compose.frankenphp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# docker-compose.frankenphp.yaml — Example FrankenPHP deployment for ChurchCRM
#
# This is a REFERENCE CONFIGURATION for self-hosted deployments using FrankenPHP
# instead of Apache or nginx + PHP-FPM. FrankenPHP bundles Caddy and PHP in a
# single container, resulting in a simpler two-service stack (web + database).
# It is NOT used by the automated test suite (which uses docker-compose.yaml
# with Apache). Use this as a starting point for your own deployment.
#
# QUICK START
# -----------
# 1. Copy this file and docker/frankenphp/Caddyfile to your deployment directory.
# 2. Copy docker/Config.php to Include/Config.php in your ChurchCRM source tree
# and set the database connection details to match the values below.
# 3. Set MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD to strong secrets.
# 4. Run: docker compose -f docker-compose.frankenphp.yaml up -d
# 5. Visit http://localhost/ - you will be redirected to the setup wizard on
# first run (no database tables) or to the login page after installation.
#
# IMPORTANT: Change all passwords before exposing this to the internet.

services:

database:
image: mariadb:11
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-changeme_root}
MYSQL_DATABASE: churchcrm
MYSQL_USER: churchcrm
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-changeme}
volumes:
- churchcrm-db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
timeout: 20s
retries: 10
hostname: database

churchcrm:
build:
context: .
dockerfile: Dockerfile.churchcrm-frankenphp
target: prod
image: churchcrm/crm:frankenphp
restart: unless-stopped
ports:
- "${WEBSERVER_PORT:-80}:80"
volumes:
# Mount ChurchCRM source (the contents of src/) here.
# Options:
# a) Replace with a bind mount to a local directory:
# - /path/to/churchcrm/src:/var/www/html
# b) Copy files into this named volume after the first `docker compose up`:
# docker cp /path/to/churchcrm/src/. churchcrm-churchcrm-1:/var/www/html/
# c) Build a custom image with COPY instructions in your own Dockerfile.
- churchcrm-www:/var/www/html
depends_on:
database:
condition: service_healthy
hostname: churchcrm

volumes:
churchcrm-db:
churchcrm-www:
Loading
Loading