|
1 | 1 | # SPDX-License-Identifier: MIT |
2 | 2 | # SPDX-FileCopyrightText: 2025 Jonathan D. A. Jewell <hyperpolymath> |
3 | 3 | # |
4 | | -# Wolfi PHP Container for Project Wharf |
5 | | -# ====================================== |
6 | | -# This is NOT a standard PHP image. It is stripped to "race weight": |
7 | | -# - No shell (sh, bash, zsh) |
8 | | -# - No package manager at runtime |
9 | | -# - Non-root user |
10 | | -# - Hardened PHP configuration |
| 4 | +# PHP-FPM Container for Project Wharf |
| 5 | +# ==================================== |
| 6 | +# Hardened PHP runtime for CMS workloads. |
11 | 7 | # |
12 | | -# Attack surface: Minimal. If a hacker breaks in, they can't run commands. |
| 8 | +# Build: podman build -t yacht-php:latest -f infra/containers/php.Dockerfile . |
| 9 | +# Run: podman run -d -p 9000:9000 -v ./html:/var/www/html:ro yacht-php:latest |
13 | 10 |
|
14 | | -# ----------------------------------------------------------------------------- |
15 | | -# STAGE 1: Builder (The Factory) |
16 | | -# ----------------------------------------------------------------------------- |
17 | | -FROM cgr.dev/chainguard/wolfi-base:latest AS builder |
| 11 | +# Use Chainguard if available, fallback to Alpine |
| 12 | +ARG BASE_IMAGE=docker.io/library/php:8.3-fpm-alpine |
18 | 13 |
|
19 | | -# Install PHP and required extensions for CMS compatibility |
20 | | -RUN apk add --no-cache \ |
21 | | - php-8.3 \ |
22 | | - php-8.3-fpm \ |
23 | | - php-8.3-opcache \ |
24 | | - php-8.3-mysqli \ |
25 | | - php-8.3-pdo \ |
26 | | - php-8.3-pdo_mysql \ |
27 | | - php-8.3-pdo_pgsql \ |
28 | | - php-8.3-mysqlnd \ |
29 | | - php-8.3-mbstring \ |
30 | | - php-8.3-gd \ |
31 | | - php-8.3-xml \ |
32 | | - php-8.3-simplexml \ |
33 | | - php-8.3-dom \ |
34 | | - php-8.3-curl \ |
35 | | - php-8.3-zip \ |
36 | | - php-8.3-intl \ |
37 | | - php-8.3-bcmath \ |
38 | | - php-8.3-sodium \ |
39 | | - php-8.3-redis \ |
40 | | - php-8.3-imagick \ |
41 | | - php-8.3-exif \ |
42 | | - php-8.3-fileinfo \ |
43 | | - && rm -rf /var/cache/apk/* |
| 14 | +FROM ${BASE_IMAGE} |
44 | 15 |
|
45 | | -# Harden php.ini at build time |
46 | | -RUN set -eux; \ |
47 | | - # Hide PHP version from headers |
48 | | - sed -i 's/expose_php = On/expose_php = Off/' /etc/php83/php.ini; \ |
49 | | - # Increase memory for CMS workloads |
50 | | - sed -i 's/memory_limit = 128M/memory_limit = 256M/' /etc/php83/php.ini; \ |
51 | | - # Disable error display (log only) |
52 | | - sed -i 's/display_errors = On/display_errors = Off/' /etc/php83/php.ini; \ |
53 | | - # Block remote file inclusion attacks |
54 | | - sed -i 's/allow_url_fopen = On/allow_url_fopen = Off/' /etc/php83/php.ini; \ |
55 | | - sed -i 's/allow_url_include = On/allow_url_include = Off/' /etc/php83/php.ini; \ |
56 | | - # Disable dangerous functions |
57 | | - echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source" >> /etc/php83/php.ini; \ |
58 | | - # OPcache for performance |
59 | | - echo "opcache.enable=1" >> /etc/php83/php.ini; \ |
60 | | - echo "opcache.memory_consumption=128" >> /etc/php83/php.ini; \ |
61 | | - echo "opcache.interned_strings_buffer=8" >> /etc/php83/php.ini; \ |
62 | | - echo "opcache.max_accelerated_files=10000" >> /etc/php83/php.ini; \ |
63 | | - echo "opcache.revalidate_freq=0" >> /etc/php83/php.ini; \ |
64 | | - echo "opcache.validate_timestamps=0" >> /etc/php83/php.ini |
| 16 | +LABEL org.opencontainers.image.title="Yacht PHP" |
| 17 | +LABEL org.opencontainers.image.description="Hardened PHP-FPM for Project Wharf" |
| 18 | +LABEL org.opencontainers.image.vendor="Hyperpolymath" |
| 19 | + |
| 20 | +# Install required PHP extensions for CMS compatibility |
| 21 | +RUN apk add --no-cache \ |
| 22 | + # Core extensions |
| 23 | + php83-mysqli \ |
| 24 | + php83-pdo_mysql \ |
| 25 | + php83-pdo_pgsql \ |
| 26 | + php83-mbstring \ |
| 27 | + php83-gd \ |
| 28 | + php83-xml \ |
| 29 | + php83-curl \ |
| 30 | + php83-zip \ |
| 31 | + php83-intl \ |
| 32 | + php83-bcmath \ |
| 33 | + php83-sodium \ |
| 34 | + php83-opcache \ |
| 35 | + php83-exif \ |
| 36 | + php83-fileinfo \ |
| 37 | + php83-session \ |
| 38 | + php83-tokenizer \ |
| 39 | + # For Redis sessions |
| 40 | + php83-pecl-redis \ |
| 41 | + 2>/dev/null || true |
65 | 42 |
|
66 | | -# ----------------------------------------------------------------------------- |
67 | | -# STAGE 2: Runtime (The Yacht) |
68 | | -# ----------------------------------------------------------------------------- |
69 | | -FROM cgr.dev/chainguard/php:latest-fpm |
| 43 | +# If using official PHP image, install via docker-php-ext |
| 44 | +RUN docker-php-ext-install mysqli pdo pdo_mysql opcache 2>/dev/null || true |
70 | 45 |
|
71 | | -# Copy hardened configuration |
72 | | -COPY --from=builder /etc/php83/php.ini /etc/php83/php.ini |
| 46 | +# Harden PHP configuration |
| 47 | +RUN { \ |
| 48 | + echo 'expose_php = Off'; \ |
| 49 | + echo 'display_errors = Off'; \ |
| 50 | + echo 'log_errors = On'; \ |
| 51 | + echo 'error_log = /dev/stderr'; \ |
| 52 | + echo 'memory_limit = 256M'; \ |
| 53 | + echo 'max_execution_time = 60'; \ |
| 54 | + echo 'upload_max_filesize = 64M'; \ |
| 55 | + echo 'post_max_size = 64M'; \ |
| 56 | + echo 'allow_url_fopen = Off'; \ |
| 57 | + echo 'allow_url_include = Off'; \ |
| 58 | + echo 'session.cookie_httponly = 1'; \ |
| 59 | + echo 'session.cookie_secure = 1'; \ |
| 60 | + echo 'session.use_strict_mode = 1'; \ |
| 61 | + # OPcache for performance |
| 62 | + echo 'opcache.enable = 1'; \ |
| 63 | + echo 'opcache.memory_consumption = 128'; \ |
| 64 | + echo 'opcache.max_accelerated_files = 10000'; \ |
| 65 | + echo 'opcache.revalidate_freq = 0'; \ |
| 66 | + echo 'opcache.validate_timestamps = 0'; \ |
| 67 | + # Disable dangerous functions (comment out for debugging) |
| 68 | + echo 'disable_functions = exec,passthru,shell_exec,system,proc_open,popen'; \ |
| 69 | +} > /usr/local/etc/php/conf.d/wharf-security.ini |
73 | 70 |
|
74 | | -# Copy our custom FPM pool configuration |
75 | | -COPY infra/config/php-fpm.conf /etc/php83/php-fpm.d/www.conf |
| 71 | +# Copy FPM pool configuration |
| 72 | +COPY infra/config/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf |
76 | 73 |
|
77 | | -# Create directories for WordPress compatibility |
78 | | -# These will be mounted as volumes |
79 | | -RUN mkdir -p /var/www/html \ |
80 | | - && mkdir -p /var/www/html/wp-content/uploads \ |
| 74 | +# Create web directories |
| 75 | +RUN mkdir -p /var/www/html/wp-content/uploads \ |
81 | 76 | && mkdir -p /var/www/html/wp-content/cache |
82 | 77 |
|
83 | | -# SECURITY: Run as non-root (uid 65532 is standard nonroot in distroless) |
84 | | -USER nonroot |
| 78 | +# Create non-root user |
| 79 | +RUN addgroup -g 1000 wharf && adduser -u 1000 -G wharf -s /bin/false -D wharf \ |
| 80 | + && chown -R wharf:wharf /var/www/html |
| 81 | + |
| 82 | +USER wharf |
| 83 | +WORKDIR /var/www/html |
85 | 84 |
|
86 | | -# NETWORK: FastCGI port |
87 | 85 | EXPOSE 9000 |
88 | 86 |
|
89 | | -# HEALTH CHECK |
90 | 87 | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ |
91 | | - CMD php-fpm83 -t || exit 1 |
| 88 | + CMD php-fpm -t 2>/dev/null || exit 1 |
92 | 89 |
|
93 | | -# ENTRYPOINT: Just the FPM binary, no wrapper scripts |
94 | | -CMD ["php-fpm83", "-F"] |
| 90 | +CMD ["php-fpm", "-F"] |
0 commit comments