Ready to use Docker image for WHMCS environment, packaging PHP-FPM, nginx,
SourceGuardian, and ionCube on top of lscr.io/linuxserver/baseimage-ubuntu:noble.
- PHP
8.3runtime (see PHP_VERSION.txt) - ionCube and SourceGuardian loaders preinstalled
- Nginx server configuration tailored for WHMCS
- WHMCS cron preinstalled
- All persistent state under a single
/configvolume - Multi-arch image supporting both
linux/amd64andlinux/arm64 - Optional
htpasswdprotection for the admin path (requires bothAUTH_USERandAUTH_PASS)
The published manifest is multi-arch. Pulling ghcr.io/darthshadow/whmcs:latest
selects the right image for your host automatically.
| Architecture | Available |
|---|---|
| x86-64 | yes |
| arm64 | yes |
---
services:
whmcs:
image: ghcr.io/darthshadow/whmcs:latest
hostname: whmcs
container_name: whmcs
environment:
- PUID=1000
- PGID=1000
- TZ=UTC
- AUTH_USER=whmcs
- AUTH_PASS=whmcs@server:2022
- WHMCS_SERVER_IP=1.1.1.1
- WHMCS_SERVER_URL=whmcs.example.com
- WHMCS_ADMIN_PATH=admin
volumes:
- /path/to/whmcs:/config
ports:
- 8043:80
restart: unless-stoppeddocker run -d \
--name=whmcs \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=UTC \
-e AUTH_USER=whmcs \
-e AUTH_PASS=whmcs@server:2022 \
-e WHMCS_SERVER_IP=1.1.1.1 \
-e WHMCS_SERVER_URL=whmcs.example.com \
-e WHMCS_ADMIN_PATH=admin \
-p 8043:80 \
-v /path/to/whmcs:/config \
--restart unless-stopped \
ghcr.io/darthshadow/whmcs:latest| Parameter | Function |
|---|---|
-p 80 |
WHMCS web UI (proxy externally for TLS) |
-e PUID=1000 |
UID for the in-container abc user |
-e PGID=1000 |
GID for the in-container abc user |
-e TZ=UTC |
Timezone, e.g. UTC, Europe/London |
-e AUTH_USER= (optional) |
Username for the admin-path HTTP-basic challenge (path set by WHMCS_ADMIN_PATH) |
-e AUTH_PASS= (optional) |
Password for the admin-path HTTP-basic challenge |
-e WHMCS_SERVER_IP= |
Host public IP — required for WHMCS license validation |
-e WHMCS_SERVER_URL= |
Host public hostname — required for WHMCS license validation |
-e WHMCS_ADMIN_PATH=admin (optional) |
Admin URL path protected by HTTP basic auth |
-v /config |
Persistent data root (see Volume layout) |
AUTH_USER and AUTH_PASS must either both be set or both be empty. Setting
only one will cause the container to refuse to start, since a partial auth
config would otherwise leave the admin path silently unprotected.
Everything stateful lives under /config. On first boot the container seeds
defaults. Most seeded files are left untouched on later boots; env-rendered
nginx files are refreshed while their tracked live copy has not been edited.
Edit on the host, then restart the container to pick up changes.
/config/
├── whmcs/ WHMCS configuration.php (canonical copy)
├── www/whmcs/ Extracted WHMCS web root (symlinked to /var/www/whmcs)
├── www/whmcs_storage/ downloads, attachments, templates_c, etc.
├── nginx/
│ ├── nginx.conf Global nginx config
│ ├── conf.d/php PHP location block (uses $WHMCS_SERVER_IP)
│ └── sites-available/whmcs Site config (uses $WHMCS_SERVER_URL, $WHMCS_ADMIN_PATH)
├── php/99-local.ini PHP overrides (php.ini-style)
├── fpm/99-local.conf php-fpm pool overrides
├── crontabs/whmcs WHMCS cron schedule
└── cron/log/ cron job logs
AUTH_USER / AUTH_PASS regenerate /etc/nginx/.htpasswd on every start, so
rotating credentials is just an env-var change plus restart. Nginx defaults to
client_max_body_size 30M;; override /config/nginx/nginx.conf if WHMCS
uploads need a different ceiling.
WHMCS_* nginx env vars are re-applied on every restart. If you edit
/config/nginx/conf.d/php or /config/nginx/sites-available/whmcs, the image
keeps your live file and writes future rendered updates to .new until you
accept that overlay.
The image includes a Docker healthcheck that probes http://127.0.0.1/ after
a 60-second start period.
| Tag | Meaning |
|---|---|
latest |
Build of main on every push and on every weekly run |
latest-deps |
Alias for the most recent weekly dependency-update build |
weekly-YYYY-MM-DD |
Specific weekly build (kept indefinitely) |
v<whmcs>-v<php> |
Pinned WHMCS+PHP combination, e.g. v9.0.4-v8.3; weekly builds apply this tag directly |
Branch layout:
main— latest stable WHMCS on the current PHP versionversion/<whmcs-major>.x-php<php>— active major-line combinations- Older
version/<whmcs-minor>-php<php>branches are historical archives
The canonical versions for any branch live in WHMCS_VERSION.txt and
PHP_VERSION.txt at the repo root.
docker pull + recreate the container. Base image and PHP/nginx packages get
the latest patches; /config is preserved.
By design, pulling a newer image does not replace WHMCS files in
/config/www/whmcs. The init script (60-whmcs) only extracts the bundled
zip when /config/www/whmcs/index.php is missing, so your customisations,
modules, and templates are never overwritten on restart.
To upgrade WHMCS itself when a new image ships a newer version, choose one of:
-
In-place via WHMCS's built-in updater (recommended for minor/patch bumps). Use Admin → Utilities → Update WHMCS. This is independent of the container and works on any image build.
-
Replace the bundled zip (recommended after pulling a new image):
# Stop the container so nothing is mid-write docker stop whmcs # Back up the current install sudo cp -a /path/to/whmcs/www/whmcs /path/to/whmcs/www/whmcs.bak # Wipe the web root so init re-extracts the bundled version sudo rm -rf /path/to/whmcs/www/whmcs # Start fresh — init will extract the image's WHMCS zip and copy # /config/whmcs/configuration.php back into place. docker start whmcs
Your
configuration.php,whmcs_storage/, custom modules inmodules/, and templates need to be re-merged from the.bakcopy afterwards. -
Restore from backup if step 2 doesn't go smoothly:
docker stop whmcs sudo rm -rf /path/to/whmcs/www/whmcs sudo mv /path/to/whmcs/www/whmcs.bak /path/to/whmcs/www/whmcs docker start whmcs
Pin to the matching version/<whmcs-major>.x-php<php> branch tag, e.g.
ghcr.io/darthshadow/whmcs:v9.0.4-v8.3, before crossing a major version.
# Build for the host architecture, using the version files at repo root
scripts/build-local.sh image-local
# Pin a specific WHMCS/PHP combo
docker buildx bake --set image.args.WHMCS_RELEASE=9.0.4 \
--set image.args.PHP_RELEASE=8.3 image-local
# Verify the loader downloads against known-good SHA256s (optional)
docker buildx bake \
--set image.args.SOURCEGUARDIAN_SHA256_AMD64=<sha> \
--set image.args.IONCUBE_SHA256_AMD64=<sha> \
image-localDirect docker buildx bake calls must set WHMCS_RELEASE; the wrapper reads
WHMCS_VERSION.txt and PHP_VERSION.txt and forwards any extra bake args.
The first build prints SHA256: lines for each loader download; copy those
values into the matching *_SHA256_* build args to pin against tamper in
subsequent builds.