`entrypoint-lsphp.sh` decided whether cac_path_parity was loaded with
printf '%s\n' "$LSPHP_INFO" | grep -q '^cac_path_parity support => enabled$'
under `set -euo pipefail`. `grep -q` exits on its first match; printf is still
writing the remaining ~40 KB of `lsphp -i`, takes SIGPIPE, exits 141, and
pipefail prefers 141 over grep's 0. The branch therefore evaluated FALSE
*because the extension was present* — present early enough to stop the reader —
and every affected container fell back to the auto_prepend normaliser that a
customer's own .user.ini silently displaces, i.e. the exact failure the
extension exists to remove. Measured on whp02 against the published
cac-lsphp:php83: 5/5 runs status=141 with pipefail, 0 without.
The race is decided by pipe capacity, which is why it reproduced on whp02 and
not on other daemons: while the payload fits the pipe the writer never blocks
and always finishes first. Forced over the limit it is deterministic — 3x the
same `lsphp -i` (122100 bytes) gives 141 every time in the built image.
Fixed by reading with here-strings, which are not pipelines at all, so there is
no second exit status for pipefail to adopt. Same grep/awk patterns; plumbing
only. Same class fixed everywhere it existed under pipefail:
* entrypoint-lsphp.sh parity probe, and the SCAN_DIR awk probe
* entrypoint-litespeed.sh SCAN_DIR probe (a bare assignment: 141 there does
not degrade, `set -e` kills PID 1), and ols_running
* entrypoint-shared-ols.sh ols_running
* render-shared-ols-config.sh site.meta parsing (`sed | head -1`): measured
141 at 6000 duplicate keys, which under `set -e`
aborts the whole render
* fpm-parity-check.sh the `php-fpm -m` pre-flight, whose whole job is to
stop a harness fault being blamed on the extension
Also: the fallback used to announce "cac_path_parity extension not loadable in
this image" for every reason the branch was reached, including its own plumbing
breaking — a false diagnosis that sends operators to rebuild a good image whose
build gate passed. Verdicts now carry the evidence they rest on, and a probe
that produced nothing is reported as a probe failure that establishes nothing
about the image. Fail-open posture is unchanged: no probe failure is fatal.
Adds scripts/tests/lsphp-info-probe.test.sh, which runs the shipped probes
(extracted verbatim, so they cannot drift from what runs in production) under
`set -euo pipefail` against a realistic ~40 KB phpinfo body, and statically
outlaws the shape repo-wide. Against trunk it fails, naming all 9 offending
lines. Wired into CI as a new Shell-Checks job, because no existing gate ever
executed the entrypoint's branch logic — the .phpt suite and the Dockerfile's
own `lsphp -i | grep -q` probe (which has no pipefail) were both green for the
release whose entrypoint declared that same extension missing.
Verified: PHP 8.3 --no-cache build green, 10/10 .phpt, 9/9 FPM harness; the
built image logs `path parity = extension` and reports `Rewriting => active`
with .from/.to populated; ext-removed and probe-broken variants each produce
their own honest message and still start.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cloud Apache Container
This is a base container for running PHP-based applications, supporting multiple PHP versions (7.4, 8.0, 8.1, 8.2, 8.3, 8.4). The default is PHP 8.3. The container is based on AlmaLinux 9 and uses Apache with mod_ssl. It is designed for both development and production use.
You must have Docker or compatible containerization software running.
What's New?
- Optimized Image: The Dockerfile has been refactored for smaller size, faster builds, and improved security. Unnecessary files and caches are removed during build.
- Pre-built Images for Each PHP Version: On every push, images for all supported PHP versions are built and pushed to the registry. You can pull the exact version you need (e.g.,
cac:php74,cac:php84, orcac:latest). - .dockerignore Added: The build context is now minimized, making builds faster and more secure.
Quick Start: Local Development with local-dev.sh
The easiest way to start a local development environment is with the provided local-dev.sh script. This script automates container setup, volume creation, log directories, and WordPress installation.
Usage Example
./local-dev.sh -n local-dev
Flags:
-nName of the container (required)-pHTTP port (default: 80)-sHTTPS port (default: 443)-rRoot path for files and database (default: current directory)-aPHP version (default: 8.3; options: 74, 80, 81, 82, 83, 84)-vEnable verbose mode-hShow help
The script will:
- Create a user directory and log folders
- Create a Docker volume for MySQL
- Start the container with the correct environment variables
- Generate helper scripts in your root path:
instance_start– Start the containerinstance_stop– Stop the containerinstance_logs– Tail Apache logsinstance_db_info– Show MySQL credentials
- Install WordPress in your web root
- Print MySQL credentials
Manual Docker Usage
You can also run the container manually:
mkdir -p local-development/domain.tld
cd local-development/domain.tld
mkdir user
mkdir -p user/logs/{apache,system}
docker run -d -it -p 80:80 -p 443:443 -e PHPVER=84 -e environment=DEV --mount type=bind,source="$(pwd)"/user,target=/home/myuser -v"$name-mysql":/var/lib/mysql -e uid=30001 -e user=myuser -e domain=localhost --name local-dev repo.anhonesthost.net/cloud-hosting-platform/cac:latest
Accessing the Container
docker exec -it local-dev /bin/bash
WordPress Installation
If using local-dev.sh, WordPress is installed automatically. For manual setup:
cat /home/myuser/mysql_creds
su - myuser
cd ~/public_html
wp core download
Then visit https://localhost (accept the SSL warning) to complete setup.
Features
- Multiple PHP Versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 (set with
PHPVERor-aflag) - Pre-built Images: Pull the image for your desired PHP version directly from the registry. No need to build locally unless customizing.
- Optimized Build: Smaller, faster, and more secure images thanks to the improved Dockerfile and
.dockerignore. - Automatic Database Setup: MariaDB is started in DEV mode, credentials are auto-generated and stored in
/home/$user/mysql_creds. - Database Backups: Cron job backs up the database every 15 minutes to
/home/$user/_db_backups. - Log Management: Log rotation compresses logs older than 3 days and deletes those older than 7 days.
- Memcached: Started automatically in DEV mode.
- SSL: Self-signed certificate enabled by default.
- Default Web Content:
/home/$user/public_htmlis the web root./pingendpoint andphpinfo.phpare available for diagnostics. - Helper Scripts:
instance_start,instance_stop,instance_logs,instance_db_info(created bylocal-dev.sh).
Environment Variables
Required:
uid– User ID for file permissionsuser– Username for file permissionsdomain– Primary domain for configuration
Optional:
environment– Set toDEVto start memcached and MySQL locally for developmentserveralias– Comma-separated list of alternative hostnamesPHPVER– PHP version (see above)
Helpful Notes
- To restart the instance:
./instance_startordocker start {container-name} - To stop:
./instance_stopordocker stop {container-name} - To view logs:
./instance_logsordocker logs -f {container-name} - To get DB credentials:
./instance_db_infoorcat /home/$user/mysql_creds - To delete a container:
docker rm {container-name}(does not delete user files or DB volume) - To view running containers:
docker ps - To view all containers:
docker ps --all - To view images:
docker images
Troubleshooting
- The first run may take several minutes as dependencies are installed.
- If you need to change PHP version, stop and remove the container, then recreate with the desired version.
- For advanced configuration, see the scripts in the
scripts/directory. - The image is optimized for size and speed, but local development in DEV mode may install additional packages (MariaDB, memcached) at runtime using microdnf.
- The build context is minimized by the included
.dockerignorefile.