Skip to content

Commit 6e46512

Browse files
CopilotDawoudIO
andauthored
docs(docker): add nginx + PHP-FPM reference configuration to fix login redirect loop (#8207)
ChurchCRM users deploying with nginx + PHP-FPM hit an infinite redirect loop (`/session/begin → /session/begin`) because nginx routes everything to the root `index.php`, which redirects unauthenticated users to `/session/begin` — which also hits `index.php` — and so on. The loop happens because ChurchCRM has **9 independent Slim 4 entry points** (one per subdirectory). Apache resolves this automatically via per-directory `.htaccess` mod_rewrite rules; nginx requires explicit `location` blocks per sub-app. ## Changes - **`docker/nginx/default.conf`** — nginx server block with `location ^~` blocks routing each sub-application prefix to its own `index.php`. Regex locations for static assets and PHP, security denies for `/logs`, `/tmp_attach`, and dotfiles. Commented subdirectory-install variant at the bottom. ```nginx # Each Slim sub-app gets its own routing block location ^~ /session { try_files $uri /session/index.php$is_args$args; location ~ \.php$ { fastcgi_pass php-fpm:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } # ... repeated for /api, /v2, /admin, /finance, /kiosk, /plugins, /external, /setup ``` - **`docker/Dockerfile.churchcrm-fpm-php8`** — PHP-FPM multi-stage image (`prod` / `dev`) with all required extensions (`bcmath`, `gd`, `intl`, `mysqli`, `pdo_mysql`, `sodium`, `zip`, …). Mirrors the existing `Dockerfile.churchcrm-apache-php8`. - **`docker/docker-compose.nginx.yaml`** — Self-contained Compose reference (MariaDB + PHP-FPM + nginx). Not wired into CI; intended as a starting point for self-hosted deployments. - **`docker/README.md`** — Added intro table distinguishing dev/CI (Apache) from self-hosted (nginx), plus a new section explaining the routing architecture, the loop pitfall, quick-start, and customisation notes. <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Question: Docker Installation Challenges</issue_title> > <issue_description>## How can we help? > > Please share briefly: > - **What you're trying to do**: I'm trying to install ChurchCRM into an environment that is otherwise manged through docker-compose. Since ChurchCRM doesn't have a dedicated set of docker containers, I've been attempting to install it using a combination of nginx, mariadb, and a custom PHP dockerfile with all the needed PHP extensions. I've made good progress (and I'm looking to share my procedures once I'm successful), but I'm running into an issue just a few feet from the finish line. > > Specifically, I'm getting a login loop after finishing the initial setup wizard, 'too many redirects'. > > From Claude: > This is the key finding! Look at what's happening: > > ✅ Cookie IS being set — CRM-xxxxx=yyy — so session saving is working now > ✅ Only one redirect with curl+cookies — not an infinite loop > ❌ It's redirecting /session/begin → /session/begin — redirecting to itself! > > > - **What you've tried**: Steps or settings you've attempted > This occurs after attempting to go to http://churchcrm.example.com, once the setup wizard has been completed. > > - **Expected behavior**: What you expected to happen > Getting a login prompt =) > > Add environment details only if relevant (version, hosting type, etc.). > > My Docker Compose: > > ```version: "3.9" > > services: > > churchcrm-mariadb: > image: mariadb:11 > container_name: churchcrm-mariadb > restart: unless-stopped > environment: > MYSQL_ROOT_PASSWORD: 12345 > MYSQL_DATABASE: churchcrm > MYSQL_USER: churchcrm > MYSQL_PASSWORD: 12345 > volumes: > - churchcrm-mariadb:/var/lib/mysql > > churchcrm-php: > image: php84-custom1 > container_name: churchcrm-php > restart: unless-stopped > volumes: > - churchcrm-php:/usr/local/etc/php/conf.d > - churchcrm-www:/var/www/html > depends_on: > - churchcrm-mariadb > > churchcrm-nginx: > image: nginx:stable-alpine > container_name: churchcrm-nginx > restart: unless-stopped > ports: > - "80:80" > volumes: > - churchcrm-www:/var/www/html > - churchcrm-nginx:/etc/nginx/ > depends_on: > - churchcrm-php > > # ========================= > # PHPMYADMIN > # ========================= > > phpmyadmin: > image: phpmyadmin/phpmyadmin:latest > container_name: churchcrm-phpmyadmin > ports: > - "7381:80" > environment: > - PMA_HOST=churchcrm-mariadb > - PMA_PORT=3306 > - TZ=America/New_York > restart: ${RESTART_POLICY} > > volumes: > churchcrm-php: > churchcrm-nginx: > churchcrm-www: > churchcrm-mariadb: > ``` > > My Dockerfile for my php container: > > ```FROM php:8.4-fpm > > RUN apt-get update && apt-get install -y \ > libzip-dev \ > libicu-dev \ > libpng-dev \ > libjpeg-dev \ > libfreetype6-dev \ > libxml2-dev \ > libonig-dev \ > unzip \ > git \ > curl \ > && docker-php-ext-configure gd --with-freetype --with-jpeg \ > && docker-php-ext-install \ > pdo_mysql \ > mysqli \ > intl \ > gd \ > zip \ > mbstring \ > xml \ > opcache \ > gettext \ > && apt-get clean > ``` > > My php override file: > > ``` > session.save_path = /tmp > upload_max_filesize = 20M > post_max_size = 25M > memory_limit = 256M > ``` > > My nginx config: > > ``` > server { > listen 80; > server_name crm.example.org; > root /var/www/html; > index index.php index.html; > client_max_body_size 100M; > > location ^~ /setup { > try_files $uri $uri/ /setup/index.php$is_args$args; > > location ~ \.php$ { > include fastcgi_params; > fastcgi_pass churchcrm-php:9000; > fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; > fastcgi_index index.php; > } > } > > location / { > try_files $uri $uri/ /index.php?$query_string; > } > > location ~ \.php$ { > include fastcgi_params; > fastcgi_pass churchcrm-php:9000; > fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; > fastcgi_index index.php; > } > > location ~ /\.ht { > deny all; > } > } > ``` > > ...I think that's everything =)</issue_description> > > <agent_instructions>review the docker setup used for dev/ci/test and recommend a path forward </agent_instructions> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #8200 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DawoudIO <554959+DawoudIO@users.noreply.github.com> Co-authored-by: George Dawoud <george@dawouds.com>
1 parent bdc157c commit 6e46512

File tree

4 files changed

+489
-2
lines changed

4 files changed

+489
-2
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# PHP-FPM Dockerfile for ChurchCRM (nginx deployments)
2+
#
3+
# Use this image with the nginx configuration in docker/nginx/default.conf.
4+
# For the Apache-based development/CI setup, see Dockerfile.churchcrm-apache-php8.
5+
#
6+
# Build:
7+
# docker build -f Dockerfile.churchcrm-fpm-php8 -t churchcrm/crm:php8-fpm .
8+
#
9+
# Required PHP extensions for ChurchCRM:
10+
# bcmath, curl, exif, gd, gettext, iconv, intl, mbstring, mysqli,
11+
# opcache, pdo_mysql, sodium, xml, zip
12+
13+
FROM php:8-fpm AS base
14+
LABEL maintainer="ChurchCRM"
15+
16+
EXPOSE 9000
17+
18+
# Install system dependencies required by PHP extensions
19+
RUN apt-get update && \
20+
apt-get install -y \
21+
libcurl4-openssl-dev \
22+
libfreetype6-dev \
23+
libicu-dev \
24+
libjpeg-dev \
25+
libonig-dev \
26+
libpng-dev \
27+
libxml2-dev \
28+
libzip-dev \
29+
gettext \
30+
locales \
31+
locales-all \
32+
unzip \
33+
&& rm -rf /var/lib/apt/lists/*
34+
35+
# Install and configure PHP extensions
36+
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
37+
docker-php-ext-install -j$(nproc) \
38+
bcmath \
39+
curl \
40+
exif \
41+
gd \
42+
gettext \
43+
iconv \
44+
intl \
45+
mbstring \
46+
mysqli \
47+
opcache \
48+
pdo_mysql \
49+
xml \
50+
zip
51+
52+
# Sodium is bundled with PHP 7.2+ but needs to be enabled
53+
RUN docker-php-ext-enable sodium
54+
55+
# Configure PHP settings
56+
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
57+
sed -i 's/^upload_max_filesize.*$/upload_max_filesize = 2G/g' $PHP_INI_DIR/php.ini && \
58+
sed -i 's/^post_max_size.*$/post_max_size = 2G/g' $PHP_INI_DIR/php.ini && \
59+
sed -i 's/^memory_limit.*$/memory_limit = 256M/g' $PHP_INI_DIR/php.ini && \
60+
sed -i 's/^max_execution_time.*$/max_execution_time = 120/g' $PHP_INI_DIR/php.ini
61+
62+
63+
# Production stage - minimal runtime
64+
FROM base AS prod
65+
66+
67+
# Dev stage - includes build tools for local development inside the container
68+
FROM base AS dev
69+
70+
# Install development tools
71+
RUN apt-get update && \
72+
apt-get install -y \
73+
git \
74+
make \
75+
python3 \
76+
unzip \
77+
curl \
78+
&& rm -rf /var/lib/apt/lists/*
79+
80+
# Install Xdebug for step-debugging
81+
RUN pecl install xdebug && docker-php-ext-enable xdebug
82+
83+
# Install Composer
84+
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
85+
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
86+
rm composer-setup.php
87+
88+
# Install NVM + Node.js (needed to run npm run deploy inside the container)
89+
RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh -o /opt/node-install.sh && \
90+
chmod a+x /opt/node-install.sh && \
91+
/opt/node-install.sh && \
92+
rm /opt/node-install.sh
93+
94+
RUN /bin/bash -c "source /root/.nvm/nvm.sh && nvm install 24 && npm install -g node-gyp"

docker/README.md

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1-
Getting Started with Docker for Development
1+
Getting Started with Docker for ChurchCRM
22
===========================
33

4+
This directory contains two types of Docker configurations:
5+
6+
| Configuration | Use Case |
7+
|---------------|----------|
8+
| `docker-compose.yaml` | Development and testing (Apache + PHP). Used by the automated test suite and local development. |
9+
| `docker-compose.nginx.yaml` | Self-hosted or production reference (nginx + PHP-FPM). Starting point for your own deployment. |
10+
11+
---
12+
13+
Development & Testing (Apache)
14+
---
15+
416
** THIS DOCKER CONFIGURATION IS INTENDED FOR DEVELOPMENT & TESTING PURPOSES ONLY**
517

618
ChurchCRM uses a single `docker-compose.yaml` with profiles to support different environments:
@@ -117,4 +129,74 @@ WEBSERVER_PORT=80
117129
ADMINER_PORT=8088
118130
MAILSERVER_PORT=1025
119131
MAILSERVER_GUI_PORT=8025
120-
```
132+
```
133+
134+
---
135+
136+
Self-Hosted / Production (nginx + PHP-FPM)
137+
---
138+
139+
The `docker-compose.nginx.yaml` and `nginx/default.conf` files provide a reference
140+
configuration for deploying ChurchCRM with **nginx + PHP-FPM** instead of Apache.
141+
This is the setup most commonly used in self-hosted environments (reverse-proxy
142+
stacks, Kubernetes, etc.).
143+
144+
### Why nginx needs explicit routing
145+
146+
ChurchCRM is structured as multiple independent **Slim 4 PHP applications**, each
147+
in its own subdirectory with its own `index.php` entry point:
148+
149+
| URL prefix | Entry point |
150+
|------------|-------------|
151+
| `/session/` | `session/index.php` — login, logout, 2FA |
152+
| `/api/` | `api/index.php` — REST API |
153+
| `/v2/` | `v2/index.php` — modern MVC pages |
154+
| `/admin/` | `admin/index.php` — admin panel |
155+
| `/finance/` | `finance/index.php` — finance module |
156+
| `/kiosk/` | `kiosk/index.php` — check-in kiosk |
157+
| `/plugins/` | `plugins/index.php` — plugin system |
158+
| `/external/` | `external/index.php` — public integrations |
159+
| `/setup/` | `setup/index.php` — first-run wizard |
160+
| `/` | `index.php` — legacy PHP pages |
161+
162+
With **Apache**, each subdirectory's `.htaccess` file automatically routes
163+
requests to the correct entry point.
164+
165+
With **nginx**, you must explicitly map each URL prefix to its entry point.
166+
**Routing all requests to the root `index.php`** (a common mistake) causes an
167+
infinite redirect loop because unauthenticated users are redirected to
168+
`/session/begin`, but that path also goes to `index.php`, which redirects again.
169+
170+
### Quick start
171+
172+
```bash
173+
# From the docker/ directory:
174+
docker compose -f docker-compose.nginx.yaml up -d
175+
```
176+
177+
Visit `http://localhost/` — you will see the setup wizard on first run.
178+
179+
### Files
180+
181+
| File | Description |
182+
|------|-------------|
183+
| `docker-compose.nginx.yaml` | Example Compose file (nginx + PHP-FPM + MariaDB) |
184+
| `nginx/default.conf` | nginx server block with correct per-subdirectory routing |
185+
| `Dockerfile.churchcrm-fpm-php8` | PHP-FPM image with all required extensions |
186+
187+
### Customising the nginx config
188+
189+
1. Copy `nginx/default.conf` to your deployment.
190+
2. Replace `php-fpm:9000` with your PHP-FPM container hostname/port.
191+
3. Set `root` to the path where ChurchCRM's `src/` contents are served from.
192+
4. For a **subdirectory install** (e.g. `http://example.com/churchcrm/`):
193+
- Set `$sRootPath = '/churchcrm'` in `Include/Config.php`.
194+
- Prefix all `location` paths in the nginx config with `/churchcrm`.
195+
- See the commented example at the bottom of `nginx/default.conf`.
196+
197+
### Required PHP extensions
198+
199+
`bcmath`, `curl`, `exif`, `gd`, `gettext`, `iconv`, `intl`, `mbstring`, `mysqli`,
200+
`opcache`, `pdo_mysql`, `sodium`, `xml`, `zip`
201+
202+
The `Dockerfile.churchcrm-fpm-php8` installs all of these.

docker/docker-compose.nginx.yaml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# docker-compose.nginx.yaml — Example nginx + PHP-FPM deployment for ChurchCRM
2+
#
3+
# This is a REFERENCE CONFIGURATION for self-hosted deployments using nginx +
4+
# PHP-FPM instead of Apache. It is NOT used by the automated test suite (which
5+
# uses docker-compose.yaml with Apache). Use this as a starting point for your
6+
# own production or staging environment.
7+
#
8+
# QUICK START
9+
# -----------
10+
# 1. Copy this file and docker/nginx/default.conf to your deployment directory.
11+
# 2. Copy docker/Config.php to Include/Config.php in your ChurchCRM source tree
12+
# and set the database connection details to match the values below.
13+
# 3. Set MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD to strong secrets.
14+
# 4. Run: docker compose -f docker-compose.nginx.yaml up -d
15+
# 5. Visit http://localhost/ - you will be redirected to the setup wizard on
16+
# first run (no Config.php) or to the login page after installation.
17+
#
18+
# IMPORTANT: Change all passwords before exposing this to the internet.
19+
20+
services:
21+
22+
database:
23+
image: mariadb:11
24+
restart: unless-stopped
25+
environment:
26+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-changeme_root}
27+
MYSQL_DATABASE: churchcrm
28+
MYSQL_USER: churchcrm
29+
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-changeme}
30+
volumes:
31+
- churchcrm-db:/var/lib/mysql
32+
healthcheck:
33+
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
34+
timeout: 20s
35+
retries: 10
36+
hostname: database
37+
38+
php-fpm:
39+
build:
40+
context: .
41+
dockerfile: Dockerfile.churchcrm-fpm-php8
42+
target: prod
43+
image: churchcrm/crm:php8-fpm
44+
restart: unless-stopped
45+
volumes:
46+
# Mount ChurchCRM source (the contents of src/) here.
47+
# Options:
48+
# a) Replace with a bind mount to a local directory:
49+
# - /path/to/churchcrm/src:/var/www/html
50+
# b) Copy files into this named volume after the first `docker compose up`:
51+
# docker cp /path/to/churchcrm/src/. churchcrm-php-fpm-1:/var/www/html/
52+
# c) Build a custom image with COPY instructions in your own Dockerfile.
53+
- churchcrm-www:/var/www/html
54+
depends_on:
55+
database:
56+
condition: service_healthy
57+
hostname: php-fpm
58+
59+
webserver:
60+
image: nginx:stable-alpine
61+
restart: unless-stopped
62+
ports:
63+
- "${WEBSERVER_PORT:-80}:80"
64+
volumes:
65+
# Same source volume as php-fpm so nginx can serve static files directly
66+
- churchcrm-www:/var/www/html:ro
67+
# Mount the nginx configuration
68+
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
69+
depends_on:
70+
- php-fpm
71+
hostname: webserver
72+
73+
volumes:
74+
churchcrm-db:
75+
churchcrm-www:

0 commit comments

Comments
 (0)