https://github.com/softcreatrmedia/frankenphp-woltlab-suite
WoltLab Suite on FrankenPHP
https://github.com/softcreatrmedia/frankenphp-woltlab-suite
caddy caddy-server docker docker-compose frankenphp php webserver woltlab woltlab-suite
Last synced: 19 days ago
JSON representation
WoltLab Suite on FrankenPHP
- Host: GitHub
- URL: https://github.com/softcreatrmedia/frankenphp-woltlab-suite
- Owner: SoftCreatRMedia
- Created: 2026-05-08T12:02:54.000Z (26 days ago)
- Default Branch: main
- Last Pushed: 2026-05-08T21:11:20.000Z (26 days ago)
- Last Synced: 2026-05-13T07:36:00.548Z (21 days ago)
- Topics: caddy, caddy-server, docker, docker-compose, frankenphp, php, webserver, woltlab, woltlab-suite
- Language: Shell
- Homepage: https://softcreatr.dev
- Size: 33.2 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# WoltLab Suite on FrankenPHP
Production-ready Docker image for WoltLab Suite Core on FrankenPHP and Caddy.
This image is intentionally small in scope:
- FrankenPHP + Caddy in one application container
- MariaDB as a separate service
- SSL, HTTP/1.1, HTTP/2, and HTTP/3 through Caddy
- WoltLab-compatible generic URL rewrites
## Supported Images
| WoltLab Suite | PHP | Default WSC Patch | Tag |
|---------------|-----|-------------------|--------------|
| 6.2 | 8.4 | 6.2.3 | `6.2-php8.4` |
| 6.1 | 8.3 | 6.1.19 | `6.1-php8.3` |
| 6.0 | 8.3 | 6.0.25 | `6.0-php8.3` |
The WoltLab patch version is a build argument and runtime download variable, so newer patch releases do not require a Dockerfile change.
## Manual Setup
This repository is primarily a Docker package. For administrators who want to set up the same stack directly on a host, see [manual/README.md](manual/README.md).
The `manual/` directory is intentionally excluded from the Docker build context and is not copied into release images.
## Quick Start
Build locally:
```sh
cp .env.example .env
docker compose up -d --build
```
Open:
```text
https://localhost/install.php
```
Database values for the installer:
```text
Host: db
Database: generated in the wsc-secrets Docker volume
User: generated in the wsc-secrets Docker volume
Password: generated in the wsc-secrets Docker volume
```
The compose file exposes these values to the WoltLab installer through `WCFSETUP_DBHOST`, `WCFSETUP_DBNAME_FILE`, `WCFSETUP_DBUSER_FILE`, and `WCFSETUP_DBPASSWORD_FILE`, matching the setup variables used by `wsc-dockerized` while avoiding a plaintext default password in the compose file.
If `MYSQL_DATABASE`, `MYSQL_USER`, or `MYSQL_PASSWORD` are empty or missing, `credential-init` generates random values once and stores them in the `wsc-secrets` Docker volume. To provide fixed values, set them before the first `docker compose up`. Existing MariaDB volumes keep their initial credentials.
## Using Prebuilt GHCR Images
Prebuilt multi-architecture images are published to:
```text
ghcr.io/softcreatrmedia/frankenphp-woltlab-suite
```
Available tags:
| WoltLab Suite | PHP | Tags |
|---------------|-----|-------------------------------|
| 6.2 | 8.4 | `6.2-php8.4`, `6.2.3-php8.4` |
| 6.1 | 8.3 | `6.1-php8.3`, `6.1.19-php8.3` |
| 6.0 | 8.3 | `6.0-php8.3`, `6.0.25-php8.3` |
Use `compose.prebuilt.yaml` to disable local builds and pull from GHCR:
```sh
cp .env.example .env
docker compose -f compose.yaml -f compose.prebuilt.yaml pull
docker compose -f compose.yaml -f compose.prebuilt.yaml up -d
```
Select a different prebuilt variant by changing `WSC_TAG` in `.env`:
```env
WSC_TAG=6.1-php8.3
```
Override `WSC_PREBUILT_IMAGE` only when using a fork or private registry:
```env
WSC_PREBUILT_IMAGE=ghcr.io/your-org/frankenphp-woltlab-suite
```
## Building
Build the default WSC 6.2 / PHP 8.4 image:
```sh
docker build \
--build-arg PHP_VERSION=8.4 \
--build-arg WSC_REF=6.2.3 \
-t frankenphp-woltlab-suite:6.2-php8.4 .
```
Build all supported variants:
```sh
docker buildx bake
```
Build one variant:
```sh
docker buildx bake wsc61_php83
```
## Runtime Configuration
Common environment variables:
| Variable | Default | Purpose |
|-------------------------------------|---------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `SERVER_NAME` | `localhost` | Caddy site address. Use your domain in production. |
| `WSC_REF` | `6.2.3` | WoltLab/WCF tag or branch used when building the installer. |
| `WCFSETUP_DBHOST` | `db` | Database host passed to the WoltLab installer. |
| `WCFSETUP_DBNAME_FILE` | `/run/wsc-secrets/db-name` | File containing the database name for the WoltLab installer. |
| `WCFSETUP_DBUSER_FILE` | `/run/wsc-secrets/db-user` | File containing the database user for the WoltLab installer. |
| `WCFSETUP_DBPASSWORD_FILE` | `/run/wsc-secrets/db-password` | File containing the database password for the WoltLab installer. |
| `MYSQL_INNODB_BUFFER_POOL_SIZE` | `1G` | MariaDB InnoDB buffer pool size. Lower this only on very small servers. |
| `PHP_MEMORY_LIMIT` | `512M` | PHP memory limit. |
| `PHP_UPLOAD_MAX_FILESIZE` | `64M` | Maximum upload file size. |
| `PHP_POST_MAX_SIZE` | `64M` | Maximum POST body size. |
| `PHP_DISABLE_FUNCTIONS` | `exec,passthru,shell_exec,system,proc_open,popen` | PHP functions disabled by default to reduce command-execution risk. Set to an empty value only if a trusted plugin requires one of them. |
| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `0` | Disable timestamp checks for production performance. Restart `wsc` after package updates that change PHP files. |
| `PHP_OPCACHE_MEMORY_CONSUMPTION` | `256` | OPcache shared memory size in MB. |
| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `20000` | Maximum number of cached PHP files. |
| `FRANKENPHP_NUM_THREADS` | `1` | FrankenPHP PHP thread count. Keep this at `1` unless you have tested installation, package updates, and style rebuilds with a higher value. |
| `FRANKENPHP_MAX_THREADS` | `1` | FrankenPHP maximum PHP thread count. Keep this aligned with `FRANKENPHP_NUM_THREADS` by default. |
| `FRANKENPHP_CONFIG` | empty | Extra FrankenPHP global config. |
| `CADDY_GLOBAL_OPTIONS` | empty | Extra Caddy global options. |
| `CADDY_SERVER_EXTRA_DIRECTIVES` | empty | Extra Caddy site directives, for example a custom `tls` directive. |
| `CERTBOT_CERT_NAME` | empty | Certificate directory name below `/etc/letsencrypt/live` when using `compose.certbot.yaml`. |
| `MYSQL_MAX_CONNECTIONS` | `300` | MariaDB connection limit. |
| `MYSQL_TABLE_OPEN_CACHE` | `4000` | MariaDB table cache size. |
| `MYSQL_THREAD_CACHE_SIZE` | `64` | MariaDB thread cache size. |
The application volume is `/app/public`. The image builds a WoltLab installer from the selected `WoltLab/WCF` GitHub tag or branch and stores it in `/usr/src/woltlab`. If `/app/public` is empty, the entrypoint copies that prebuilt installer into the volume.
## Hardened Runtime
The default compose file runs the application container with a hardened profile:
- non-root `www-data` user
- read-only root filesystem
- `no-new-privileges`
- all Linux capabilities dropped except `NET_BIND_SERVICE`
- tmpfs-backed `/tmp` with `noexec,nosuid`
- writable mounts only for `/app/public`, `/data`, and `/config`
### SSL / Certbot Certificate
The default Caddy behavior is usually enough for public DNS names: set `SERVER_NAME` to the domain and Caddy will request and renew the certificate itself. Use Certbot only if you want the host to manage certificates, or if you need a certificate type that Caddy cannot request for you.
To request a new certificate on the host, stop anything that is using ports 80 or 443 and run Certbot in standalone mode:
```sh
apt-get update
apt-get install -y certbot
docker compose -f compose.yaml down
certbot certonly --standalone -d example.com
```
Then copy the host-managed certificate into a Docker volume at startup and tell Caddy to use it:
```env
SERVER_NAME=example.com
CERTBOT_CERT_NAME=example.com
CADDY_SERVER_EXTRA_DIRECTIVES=tls /certs/fullchain.pem /certs/privkey.pem
```
```sh
docker compose -f compose.yaml -f compose.certbot.yaml up -d --build
```
When using GHCR images, include `compose.prebuilt.yaml` and omit `--build`:
```sh
docker compose -f compose.yaml -f compose.prebuilt.yaml -f compose.certbot.yaml up -d
```
For an IP address certificate, set both `SERVER_NAME` and `CERTBOT_CERT_NAME` to the IP address. Set `CADDY_GLOBAL_OPTIONS` to `default_sni ` as well, because many clients omit SNI when connecting directly to an IP address.
After Certbot renews a host-managed certificate, refresh the Docker copy and restart Caddy:
```sh
docker compose -f compose.yaml -f compose.certbot.yaml run --rm certbot-cert-init
docker compose -f compose.yaml -f compose.certbot.yaml restart wsc
```
## HTTP/3
Expose UDP 443 as well as TCP 443:
```yaml
ports:
- "443:443/tcp"
- "443:443/udp"
```
Caddy will advertise HTTP/3 automatically when TLS is active.
## URL Rewrites
The Caddyfile implements the common WoltLab rewrite pattern:
```text
/app/path -> /app/index.php?path if /app/index.php exists
/foo/bar -> /index.php?foo/bar
```
Existing files and directories are served directly.
After installation, enable WoltLab's matching application setting in:
```text
ACP -> Configuration -> Options -> General -> Enable URL rewrite
```
You can validate the ACP system check and rewritten ACP routes with:
```sh
WSC_BASE_URL=https://example.com \
WSC_ADMIN_USER=admin \
WSC_ADMIN_PASSWORD='change-me' \
WSC_ENABLE_URL_REWRITE=1 \
npm run verify:production
```
## Security Notes
The bundled Caddyfile:
- hides the `Server` header
- disables `expose_php`
- denies dotfiles except `/.well-known/*`
- denies common archive, backup, config, SQL, key, certificate, shell, and template files
- denies direct access to `/vendor/*`
- denies direct access to selected non-public PHP paths
- sends long-lived immutable cache headers for static assets
Worker mode is not enabled. WoltLab Suite has not been audited for a persistent PHP worker lifecycle, and classic request isolation is the safer production default.
## Backups
Create a database, application volume, and generated-secrets backup:
```sh
scripts/backup.sh
```
Restore on a clean deployment:
```sh
scripts/restore.sh backups/
```
If you restore a deployment that uses additional compose overlays, pass the same compose files through `COMPOSE_ARGS`:
```sh
COMPOSE_ARGS="-f compose.yaml -f compose.certbot.yaml" \
scripts/restore.sh backups/
```
## Updating WoltLab
Use WoltLab's built-in package updater for installed communities. The image bootstraps new installations only; it does not overwrite an existing `/app/public` volume.
For new images on a newer patch release:
```sh
WSC_REF=6.2.4 docker compose up -d --build
```
`WSC_REF` accepts a tag such as `6.2.3` or a branch such as `6.2`.