This Docker image provides an Apache server with an optional PHP-FPM backend. It allows for dynamic enabling of Apache modules and adjusts server settings based on environment variables.
You can find our builds here:
https://hub.docker.com/r/mxmd/httpd/tags
ie:
docker pull mxmd/httpd:2.4First, make sure you've cloned the repository and you are inside the directory containing the Dockerfile:
docker build -t mxmd/httpd:2.4.66 .The Dockerfile defaults currently target Apache 2.4.66 on Alpine 3.23. You can still override them when needed:
docker build \
--build-arg APACHE_VERSION=2.4.66 \
--build-arg ALPINE_VERSION=3.23 \
-t mxmd/httpd:2.4.66 .You can run the image using:
docker run -d -p 80:80 -p 443:443 mxmd/httpd:2.4.66The default mxmd/httpd tags keep the original startup behavior and may modify Apache config files in place during container startup.
For read-only filesystem support, use the hardened tags instead:
mxmd/httpd:2.4.66-hardenedmxmd/httpd:2.4-hardenedmxmd/httpd:2.4.66-hardened-nonrootmxmd/httpd:2.4-hardened-nonroot
Build the hardened image locally with the default versions:
docker build -f Dockerfile.hardened -t mxmd/httpd:2.4.66-hardened .Or override the base versions explicitly:
docker build \
--build-arg APACHE_VERSION=2.4.66 \
--build-arg ALPINE_VERSION=3.23 \
-f Dockerfile.hardened \
-t mxmd/httpd:2.4.66-hardened .Build the non-root hardened image locally:
docker build -f Dockerfile.hardened-nonroot -t mxmd/httpd:2.4.66-hardened-nonroot .The standard hardened image keeps Apache listening on ports 80 and 443 and uses /tmp for generated runtime files.
It supports --read-only by generating runtime Apache overrides into HTTPD_RUNTIME_CONF_DIR and writing pid or mutex state into HTTPD_RUNTIME_DIR.
By default those paths are:
HTTPD_RUNTIME_CONF_DIR=/tmp/httpd-conf.dHTTPD_RUNTIME_DIR=/tmp/httpd-runtime
When using --read-only, mount /tmp as writable:
docker run -d \
--read-only \
--tmpfs /tmp \
-p 80:80 \
-p 443:443 \
mxmd/httpd:2.4.66-hardenedDocker Compose example:
services:
httpd:
image: mxmd/httpd:2.4.66-hardened
read_only: true
tmpfs:
- /tmp
ports:
- "80:80"
- "443:443"If you want the generated config and runtime state to persist across restarts, mount a Docker volume instead:
docker volume create httpd-tmp
docker run -d \
--read-only \
-v httpd-tmp:/tmp \
-p 80:80 \
-p 443:443 \
mxmd/httpd:2.4.66-hardenedThe non-root hardened image runs as a dedicated httpd user with default UID/GID 1000:1000 and listens on unprivileged port 8080, so you can publish host port 80 to container port 8080 without granting privileged port access inside the container.
By default it uses:
HTTPD_RUNTIME_CONF_DIR=/tmp/httpd-conf.dHTTPD_RUNTIME_DIR=/tmp/httpd-runtime
Use it like this:
docker run -d \
--read-only \
--tmpfs /tmp \
-p 80:8080 \
mxmd/httpd:2.4.66-hardened-nonrootIf you want the container process to match the host user that owns a bind mount, override it with --user:
docker run -d \
--read-only \
--tmpfs /tmp \
--user "$(id -u):$(id -g)" \
-v "$PWD:/usr/local/apache2/htdocs:ro" \
-p 80:8080 \
mxmd/httpd:2.4.66-hardened-nonrootThis is useful when mounted files are not readable by UID/GID 1000:1000. The mounted content still needs normal Linux read and execute permissions for the chosen user, and /tmp still needs to be writable by that user when you run the image as read-only.
When you mount your own vhost files into conf/vhosts, update them to use <VirtualHost *:8080> so they match the non-root listener. Publish the host port to container port 8080, for example -p 80:8080 or Compose ports: ["8080:8080"].
The non-root image also uses a dedicated internal healthcheck vhost on *:8080 and probes it via 127.0.0.1, so container health does not depend on privileged port bindings or IPv6 localhost name resolution.
If you want a stricter split between config generation and the Apache listener, you can provision the runtime files into a Docker volume first and then run the hardened container in consume-only mode.
Create a volume:
docker volume create httpd-runtimePopulate it with the runtime config:
docker run --rm \
-e HOST_ENV=production \
-e APACHE_MODULES="ratelimit_module modules/mod_ratelimit.so" \
-v httpd-runtime:/tmp \
mxmd/httpd:2.4.66-hardened \
init-httpdThen start Apache using the provisioned files:
docker run -d \
--read-only \
-e HTTPD_SKIP_GENERATION=1 \
-v httpd-runtime:/tmp \
-p 80:80 \
-p 443:443 \
mxmd/httpd:2.4.66-hardenedDocker Compose example:
services:
init-httpd:
image: mxmd/httpd:2.4.66-hardened
command: ["init-httpd"]
environment:
HOST_ENV: production
APACHE_MODULES: ratelimit_module modules/mod_ratelimit.so
volumes:
- httpd-runtime:/tmp
httpd:
image: mxmd/httpd:2.4.66-hardened
read_only: true
depends_on:
init-httpd:
condition: service_completed_successfully
environment:
HTTPD_SKIP_GENERATION: "1"
volumes:
- httpd-runtime:/tmp
ports:
- "80:80"
- "443:443"
volumes:
httpd-runtime:If your Compose version does not support condition: service_completed_successfully, run init-httpd once before starting httpd:
docker compose run --rm init-httpd
docker compose up -d httpdWhen HTTPD_SKIP_GENERATION=1, the container will fail fast if the required runtime files are missing from the mounted volume.
The following Apache modules come enabled by default:
- core_module (static): Fundamental module for the operation of the Apache server.
- so_module (static): Enables the loading of dynamic modules.
- http_module (static): Core module for processing HTTP requests.
- mpm_event_module (shared): Multi-Processing Module designed for high performance sites.
- authn_file_module (shared): Provides file-based authentication.
- authn_core_module (shared): Core authentication module.
- authz_host_module (shared): Provides host-based authorization.
- authz_groupfile_module (shared): Group file-based authorization.
- authz_user_module (shared): User-based authorization.
- authz_core_module (shared): Core authorization framework.
- access_compat_module (shared): Provides access control compatibility.
- auth_basic_module (shared): Basic authentication.
- reqtimeout_module (shared): Sets timeout for reading request headers and body.
- filter_module (shared): Content filter framework.
- deflate_module (shared): Handles compression of content before it is delivered to the client.
- mime_module (shared): Maps file extensions to MIME types.
- log_config_module (shared): Provides flexible logging capabilities.
- env_module (shared): Provides environment variable manipulation.
- expires_module (shared): Enables control over the setting of Expires and Cache-Control HTTP headers.
- headers_module (shared): Provides directives to manipulate HTTP response headers.
- setenvif_module (shared): Allows conditional setting of environment variables.
- version_module (shared): Determines the configuration layout version.
- proxy_module (shared): Provides basic proxy capabilities.
- proxy_fcgi_module (shared): Enables support for FastCGI proxy.
- unixd_module (shared): UNIX daemon-related functionality.
- status_module (shared): Provides server status information.
- autoindex_module (shared): Generates directory listings.
- dir_module (shared): Handles directory index files.
- alias_module (shared): Provides mapping of URLs to file system locations.
- rewrite_module (shared): Provides URL manipulation capabilities.
You can enable additional Apache modules at runtime by passing the APACHE_MODULES environment variable. This variable should contain comma-separated values of the module's name and its path.
For example, to enable the ratelimit_module and allowmethods_module:
docker run -d -p 80:80 -p 443:443 -e APACHE_MODULES="ratelimit_module modules/mod_ratelimit.so,allowmethods_module modules/mod_allowmethods.so" mxmd/httpd:2.4.66This setup ensures flexibility in managing your Apache modules based on different requirements.
The Docker image allows you to toggle between production and development settings by using the HOST_ENV environment variable:
-
Production Mode: Hide server details.
docker run -d -p 80:80 -p 443:443 -e HOST_ENV=production mxmd/httpd:2.4.66
-
Development Mode: Display full server details.
docker run -d -p 80:80 -p 443:443 -e HOST_ENV=development mxmd/httpd:2.4.66
| Workflow | Trigger | Behavior |
|---|---|---|
| Release | Push to master; manual run; reusable workflow call |
Builds and publishes all three image variants (standard, hardened, hardened-nonroot), publishes attestations, image tags, and GitHub releases. |
| Check HTTPD Alpine Version Updates | Daily at 09:00 UTC; manual run | Fetches the latest HTTPD Alpine patch version, commits the updated .env directly to the branch, then calls the release workflow to publish changed images. |
| Scan Docker Images for Fixes | Daily at 11:00 UTC; manual run | Scans published images with Docker Scout and triggers fresh rebuilds only for images with fixable CVEs. |
Publishing builds use Docker Buildx and QEMU for linux/amd64 and linux/arm64. They publish maximum-mode provenance, SBOM attestations, and these Docker Hub tags:
| Image variant | Published tag examples |
|---|---|
| Standard | 2.4.68, 2.4, 2.4.68-YYYYMMDDHHMM, 2.4-YYYYMMDDHHMM |
| Hardened | 2.4.68-hardened, 2.4-hardened, 2.4.68-hardened-YYYYMMDDHHMM |
| Hardened non-root | 2.4.68-hardened-nonroot, 2.4-hardened-nonroot, 2.4.68-hardened-nonroot-YYYYMMDDHHMM |
GitHub releases are created for new image-tag prefixes and for each new published manifest digest. Digest release notes include the Docker digest, published tags, and digest references.
The shared release workflow accepts these inputs from other workflows:
| Input | Purpose |
|---|---|
ref |
Checks out the commit to build and uses it as the GitHub release target. |
fresh_build |
Pulls base images and disables the build cache. Scout-triggered rebuilds enable this. |
cve_list |
Comma-separated CVE IDs patched in this build; appended to the release notes. |
The HTTPD version updater reads the latest patch release from the Apache downloads index via update_httpd_alpine_versions.py. When a new patch version is detected it updates .env, commits directly to the branch, and calls the release workflow to publish the updated images.
The Scout workflow scans the published rolling tags for all three variants. Only CVEs with available fixes trigger rebuilds. Rebuilds pull base images and rebuild every layer so available OS-level fixes are picked up. Detailed Markdown reports are saved in the scout-reports artifact for 30 days.
Open the repository's Actions tab, select a workflow, choose Run workflow, and select the branch. Use Release for a full publishing build, Check HTTPD Alpine Version Updates to pull the latest patch version, or Scan Docker Images for Fixes to check for available CVE fixes.
| Credential | Use |
|---|---|
DOCKER_HUB_USERNAME |
Repository secret used to log in to Docker Hub. |
DOCKER_HUB_ACCESS_TOKEN |
Repository secret for publishing to mxmd/httpd and authenticating Docker Scout. |
GITHUB_TOKEN |
Supplied automatically by GitHub Actions for repository access, update commits, and GitHub releases. |
Upon starting the Docker container, the enabled Apache modules are printed to the console. To view them, you can check the container logs:
docker logs [container_id]Replace [container_id] with your running container's ID.