PHP Docker images for DevPanel — multi-version, multi-variant images covering PHP 7.4 through 8.3 with Apache, ModSecurity, Redis, code-server (VS Code in the browser), Composer, Drush, WP-CLI and more.
- Image variants
- Quick start (users)
- Environment variables
- Contributing
- Testing
- CI / CD
- Agent notes
- Code-server artifact pinning
Each PHP version ships three layered variants.
| Variant | Tag suffix | What it adds |
|---|---|---|
base |
X.Y-base |
PHP + Apache + code-server + Composer + Drush + WP-CLI |
secure |
X.Y-secure |
base + ModSecurity / OWASP Core Rule Set |
advance |
X.Y-advance |
secure + Redis + Supervisor |
Release candidate tags end with -rc (e.g. 8.3-base-rc).
The bundled legacy devpanel CLI is AMD64-only, including in ARM64 images.
Executing it on an ARM64 host requires AMD64 emulation on that host; CI runner
emulation is not included in the published images.
Available PHP versions: 7.4 · 8.0 · 8.1 · 8.2 · 8.3
# Pull the latest stable PHP 8.3 advance image
docker pull devpanel/php:8.3-advance
# Run with code-server enabled on port 8080
docker run -p 80:80 -p 8080:8080 devpanel/php:8.3-advanceOpen http://localhost for the web application and http://localhost:8080
for the browser-based editor.
| Variable | Default | Description |
|---|---|---|
APP_ROOT |
/var/www/html |
Application root directory |
WEB_ROOT |
$APP_ROOT |
Document root for Apache |
PHP_MEMORY_LIMIT |
4096M |
PHP memory_limit |
PHP_MAX_EXECUTION_TIME |
600 |
PHP max_execution_time |
PHP_MAX_INPUT_TIME |
600 |
PHP max_input_time |
PHP_MAX_INPUT_VARS |
3000 |
PHP max_input_vars |
PHP_UPLOAD_MAX_FILESIZE |
64M |
PHP upload_max_filesize |
PHP_POST_MAX_SIZE |
64M |
PHP post_max_size |
PHP_CLEAR_ENV |
false |
PHP-FPM clear_env |
SERVER_NAME |
default |
Apache ServerName |
GIT_BRANCH |
master |
Default Git branch |
CODES_PORT |
8080 |
code-server listen port |
CODES_ENABLE |
yes |
Set to no to disable code-server |
CODES_AUTH |
(unset) | Set to yes to require a password |
<php-version>/
base/ # Base image sources (per-version overlay)
secure/ # Secure image sources (per-version overlay)
advance/ # Advance image sources (per-version overlay)
base/ # Shared base stage sources (common across all versions)
secure/ # Shared secure stage sources
advance/ # Shared advance stage sources
docker-bake.hcl # Docker Bake build matrix
.github/
workflows/ # GitHub Actions CI / build workflows
.githooks/ # Git hooks (activated with ./setup-hooks.sh)
tests/
baselines/ # Stored violation counts for each linter
detect-versions.sh # Shared version-detection script (used by tests and CI)
*.sh # Lint, build, and functional test scripts
compare-baseline.py # Shared baseline-comparison helper
test.sh # Run all checks locally
setup-hooks.sh # Configure Git to use .githooks/ (run once after cloning)
.yamllint.yml # yamllint configuration
-
Copy an existing version directory (e.g.
cp -r 8.3 8.4). -
Update the
FROMline and any version-specific package pins. -
Update
docker-bake.hclif any bake variables need adjusting (the matrix auto-generates build targets for everyX.Y/directory; no per-version workflow files are needed). -
Run
./test.shto make sure there are no new lint or build violations. -
If the new Dockerfiles introduce violations that are intentional or unavoidable, update the baseline:
./test.sh --update-baseline git add tests/baselines/
Run any individual suite with --update-baseline:
bash tests/lint-dockerfile.sh --update-baseline
bash tests/lint-shell.sh --update-baseline
bash tests/lint-yaml.sh --update-baselineOr update all baselines at once:
./test.sh --update-baselineCommit the changed tests/baselines/*.json files together with the code
change that justifies the update.
| Tool | Purpose | Install |
|---|---|---|
shellcheck |
Shell script linting | apt install shellcheck |
yamllint |
YAML linting | pip install yamllint |
docker or hadolint |
Dockerfile linting | docker.com or hadolint releases |
docker |
Docker build tests | docker.com |
./test.sh./test.sh yaml # YAML lint only
./test.sh shell # Shell lint only
./test.sh dockerfile # Dockerfile lint only
./test.sh build # Docker build tests only
./test.sh run # Docker functional (run) tests only
./test.sh build run --version 8.2 # Build and run tests for a single PHP versiontests/build-dockerfile.sh builds every Dockerfile in the repository (without
pushing) to verify the images are buildable. Images are built in dependency
order per PHP version using docker buildx bake:
base → secure (built FROM base) → advance (built FROM secure)
Locally-created test tags are cleaned up by run-dockerfile.sh after
functional tests complete (only the images tested in that invocation are
removed). To remove all devpanel-php-test:* images at once, run:
bash tests/cleanup-test-images.shBuild tests take several minutes per PHP version because the images download packages (code-server, composer, etc.). Run a single version during development to keep the feedback loop short:
./test.sh build --version 8.2tests/run-dockerfile.sh starts each built image as a short-lived container
and verifies core tools and extensions are working:
| Variant | Checks |
|---|---|
base |
php --version, PHP code execution, composer --version, apache2 -v, pdo/mbstring extensions |
secure |
All base checks + mod_security2.so present |
advance |
All base checks + redis-cli --version |
Run tests require images built by the build suite. Run both together:
./test.sh build run --version 8.2Tip: Set
DEVPANEL_BUILD_ON_PUSH=1before agit pushto have the pre-push hook also run build and functional tests for any changed Dockerfiles. By default the hook runs lint only (fast); CI always runs the full suite.
Lint checks cover all severities including style warnings. Each baseline
file (tests/baselines/*.json) records the number of times each lint rule
fires per file. A check fails only when the count for any rule/file pair
increases above its baseline value — or when a rule appears in a file
where it was not present before. Reducing violations (improving the code) is
always accepted silently.
This means:
- Existing known violations do not block your push or the CI build.
- Any new violation introduced by your change will block the push and must be fixed (or explicitly added to the baseline with a justification commit).
./setup-hooks.shThis sets core.hooksPath = .githooks in the local Git config so the hooks
in .githooks/ are used directly — no copying needed. The hooks stay in
sync with the repository automatically.
After running setup-hooks.sh, git push will automatically lint only the
files you changed. Set DEVPANEL_BUILD_ON_PUSH=1 to also run Docker build
and functional tests for any changed Dockerfiles.
| Workflow | Trigger | Purpose |
|---|---|---|
test.yml |
push to main/develop; pull_request (any branch) |
Lint, build & functional checks (required) |
docker-build.yml |
push to main or develop; workflow_dispatch |
Detect changed versions and build images |
test.yml runs lint, build, and functional tests in parallel. Configure branch protection
rules in GitHub to require the Test workflow checks to pass before pull
requests to main or develop can be merged, ensuring no broken Dockerfile
or script reaches the publish workflows. (Test is the check name shown in the
branch protection UI.) See the
GitHub docs on required status checks
for setup instructions.
| Secret | Description |
|---|---|
DOCKERHUB_USERNAME |
Docker Hub username |
DOCKERHUB_TOKEN |
Docker Hub access token |
- Source files live under both per-version directories (
7.4/,8.0/, …) and shared top-level directories (base/,secure/,advance/). Changes to a shared directory affect all versions; changes underX.Y/affect only that version. Version selection for both local tests and CI is handled bytests/detect-versions.sh. - Lint baselines are stored as JSON in
tests/baselines/. Update them with./test.sh --update-baselinewhenever you make a change that intentionally alters lint counts. - Build tests (
tests/build-dockerfile.sh) try to build every Dockerfile without pushing. Functional tests (tests/run-dockerfile.sh) then start each built image and verify PHP, Apache, Composer, and extensions work. Run./test.sh build run --version <v>to test a single PHP version quickly. - The build workflow is
docker-build.yml, which handles both push-triggered and manual dispatch builds. It runs a detect → build (per-platform, parallel) → merge pipeline. ARM64 builds use nativeubuntu-26.04-armrunners; AMD64 builds useubuntu-26.04. QEMU is set up on ARM runners to execute the bundled AMD64 DevPanel CLI, and for cross-platform builds. Native AMD64 builds skip QEMU. - The
advancevariant depends onsecure, which depends onbase. The build chain is enforced via the bake target dependency graph.
base/Dockerfile defines CODESERVER_VERSION (currently 4.99.4) as the default version.
SHA256 checksum verification is always applied during the build.
The CI "Verify code-server .deb hashes" job independently downloads both platform packages and confirms they match the hashes stored in the Dockerfile.
For the pinned hash version, keep both hashes in sync:
CODESERVER_DEB_SHA256_AMD64CODESERVER_DEB_SHA256_ARM64
If you choose to pin another version the same way, compute new hashes with:
VERSION=4.99.4
for arch in amd64 arm64; do
TMPDIR=$(mktemp -d)
curl -fsSL --retry 5 --retry-all-errors --connect-timeout 10 \
"https://github.com/coder/code-server/releases/download/v${VERSION}/code-server_${VERSION}_${arch}.deb" \
-o "${TMPDIR}/code-server_${VERSION}_${arch}.deb"
shasum -a 256 "${TMPDIR}/code-server_${VERSION}_${arch}.deb"
rm -rf "${TMPDIR}"
doneThen update CODESERVER_VERSION, CODESERVER_DEB_SHA256_AMD64, and CODESERVER_DEB_SHA256_ARM64 in base/Dockerfile.