From 32b76a3c64ef6a970053ca8eb287337a58c56cde Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 08:00:48 -0500 Subject: [PATCH 01/48] Update PHP patch versions to 8.1.28, 8.2.18, and 8.3.6 --- scripts/conf/php-versions-base-config.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 84dd721a..ab5bdbdf 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -23,24 +23,21 @@ php_versions: - name: bookworm default: true patch_versions: - - 8.1.26 - - 8.1.27 + - 8.1.28 - minor: "8.2" base_os: - name: alpine - name: bookworm default: true patch_versions: - - 8.2.14 - - 8.2.15 + - 8.2.18 - minor: "8.3" base_os: - name: alpine - name: bookworm default: true patch_versions: - - 8.3.1 - - 8.3.2 + - 8.3.6 php_variations: - name: cli default: true From 8239cc3b7190cad412842931b2ed88afce6aae5a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 10:53:12 -0500 Subject: [PATCH 02/48] Removed NGINX_USER variable --- .../docs/6.reference/1.environment-variable-specification.md | 1 - src/variations/fpm-nginx/Dockerfile | 1 - 2 files changed, 2 deletions(-) diff --git a/docs/content/docs/6.reference/1.environment-variable-specification.md b/docs/content/docs/6.reference/1.environment-variable-specification.md index 4a0c45a2..2363c9fa 100644 --- a/docs/content/docs/6.reference/1.environment-variable-specification.md +++ b/docs/content/docs/6.reference/1.environment-variable-specification.md @@ -35,7 +35,6 @@ We like to customize our images on a per app basis using environment variables. `DISABLE_DEFAULT_CONFIG`
*Default: "false"*|Get full customization of the image and disable all default configurations and automations.| all `LOG_OUTPUT_LEVEL`
*Default: "warn"*|Set your container output different verbosity levels: debug, info, off |all `NGINX_SERVER_TOKENS`
*Default: "off"*|Display NGINX version in responses. (Official Docs)|fpm-nginx -`NGINX_USER`
*Default: "nginx"*|Set which user should run NGINX. (Official Docs)|fpm-nginx `NGINX_WEBROOT`
*Default: "`$APP_BASE_DIR`/public"*|Sets the root directory for requests. (Official Docs)|fpm-nginx `PHP_DATE_TIMEZONE`
*Default: "UTC"*|Control your timezone. (Official Docs)|all `PHP_DISPLAY_ERRORS`
*Default: Off*|Show PHP errors on screen. (Official docs)|all diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index 56741790..a79ec775 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -74,7 +74,6 @@ ENV APP_BASE_DIR=/var/www/html \ DISABLE_DEFAULT_CONFIG=false \ LOG_OUTPUT_LEVEL=warn \ NGINX_SERVER_TOKENS=off \ - NGINX_USER=nginx \ PHP_DATE_TIMEZONE="UTC" \ PHP_DISPLAY_ERRORS=Off \ PHP_DISPLAY_STARTUP_ERRORS=Off \ From c0fa1f32550fdde4b054b57e211d0d53fec7beb4 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 10:54:55 -0500 Subject: [PATCH 03/48] Changed FPM-NGINX to be unprivileged --- ...cker-php-serversideup-set-file-permissions | 105 ++++++++++++++++++ .../docker-php-serversideup-pool.conf | 4 +- .../entrypoint.d/10-init-webserver-config.sh | 3 +- .../etc/s6-overlay/s6-rc.d/apache2/data/check | 2 +- src/variations/fpm-nginx/Dockerfile | 6 +- .../fpm-nginx/etc/nginx/nginx.conf.template | 1 - .../etc/nginx/site-opts.d/http.conf.template | 4 +- .../etc/nginx/site-opts.d/https.conf.template | 4 +- .../etc/nginx/sites-available/ssl-full | 16 ++- .../etc/s6-overlay/s6-rc.d/nginx/data/check | 2 +- src/variations/fpm/Dockerfile | 2 + 11 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 src/common/usr/local/bin/docker-php-serversideup-set-file-permissions diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions new file mode 100644 index 00000000..cd116c32 --- /dev/null +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -0,0 +1,105 @@ +#!/bin/sh +set -e + +################################################### +# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE +################################################### +# This script is intended to be called on build for sysadmins who want to +# change the UID and GID of a specific user. This is useful for when you +# want to match the UID and GID of the host machine to the container. +# Specifically, this can be helpful to call during a build target in development +# so developers don't need to worry about permissions issues. +script_name="docker-php-serversideup-set-file-permissions" + +# Usage function +usage() { + echo "Usage: $0 --owner USER:GROUP --service SERVICE" + exit 1 +} + +# Check for minimum number of arguments +if [ "$#" -ne 4 ]; then + usage +fi + +# Parse arguments +while [ "$#" -gt 0 ]; do + case "$1" in + --owner) + OWNER="$2" + shift 2 + ;; + --service) + SERVICE="$2" + shift 2 + ;; + *) + usage + ;; + esac +done + +# Ensure OWNER and SERVICE are set +if [ -z "$OWNER" ] || [ -z "$SERVICE" ]; then + usage +fi + +# Detect the operating system using /etc/os-release +if [ -f "/etc/os-release" ]; then + . /etc/os-release + OS="$ID" +else + echo "$script_name: Cannot determine the operating system." + exit 1 +fi + +# Function to change ownership of directories +change_ownership() { + for dir in "$@"; do + if [ -d "$dir" ]; then + chown -R "$OWNER" "$dir" + echo "$script_name: Ownership of $dir changed to $OWNER." + else + echo "$script_name: Directory not found: $dir" + fi + done +} + +# Determine directories based on OS and SERVICE +case "$OS" in + debian) + case "$SERVICE" in + apache) + DIRS="/var/www/html /etc/apache2/sites-available" + ;; + nginx) + DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/" + ;; + *) + echo "$script_name: Unsupported service: $SERVICE" + exit 1 + ;; + esac + ;; + alpine) + case "$SERVICE" in + apache) + DIRS="/var/www/localhost/htdocs /etc/apache2" + ;; + nginx) + DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/lib/nginx" + ;; + *) + echo "$script_name: Unsupported SERVICE: $SERVICE" + exit 1 + ;; + esac + ;; + *) + echo "$script_name: Unsupported operating system: $OS" + exit 1 + ;; +esac + +# Change ownership of the directories +change_ownership $DIRS \ No newline at end of file diff --git a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf index 2c159a40..33018e49 100644 --- a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf +++ b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf @@ -38,8 +38,8 @@ log_limit = 8192 ; --allow-to-run-as-root option to work. ; Default Values: The user is set to master process running user by default. ; If the group is not set, the user's group is used. -user = www-data -group = www-data +;user = www-data +;group = www-data ; The address on which to accept FastCGI requests. ; Valid syntaxes are: diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index 45556ef0..e6c603f5 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -138,8 +138,7 @@ validate_ssl(){ return 0 fi - echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." - mkdir -p /etc/ssl/private/ + echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.gitpod.io,*.ngrok.io,*.nip.io" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 } diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index 30ed2fa8..97057235 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -1,5 +1,5 @@ #!/command/with-contenv sh -response=$(curl --location --insecure --silent http://localhost/ping) +response=$(curl --location --insecure --silent http://localhost:8080/ping) if [ "$response" = "pong" ]; then exit 0 diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index a79ec775..4038c3eb 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -159,7 +159,11 @@ COPY --chmod=755 src/php-fpm.d/ / # install composer from Composer's official Docker image COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -EXPOSE 80 443 +RUN docker-php-serversideup-set-file-permissions --owner www-data:www-data --service nginx + +USER www-data + +EXPOSE 8080 8443 WORKDIR ${APP_BASE_DIR} diff --git a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template index d815833d..4fe4f8e4 100644 --- a/src/variations/fpm-nginx/etc/nginx/nginx.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/nginx.conf.template @@ -1,4 +1,3 @@ -user $NGINX_USER; worker_processes auto; error_log /dev/stderr $LOG_OUTPUT_LEVEL; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template index f5f83ad8..d9edf99e 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template @@ -1,5 +1,5 @@ -listen 80 default_server; -listen [::]:80 default_server; +listen 8080 default_server; +listen [::]:8080 default_server; root $NGINX_WEBROOT; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template index a6a9af8c..ac90203f 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template @@ -1,5 +1,5 @@ -listen 443 http2 ssl default_server; -listen [::]:443 http2 ssl default_server; +listen 8443 http2 ssl default_server; +listen [::]:8443 http2 ssl default_server; root $NGINX_WEBROOT; diff --git a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full index 325d4351..bd409da8 100644 --- a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full +++ b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full @@ -1,13 +1,21 @@ # HTTP Redirect configuration # server { - listen 80 default_server; - listen [::]:80 default_server; + listen 8080 default_server; + listen [::]:8080 default_server; server_name _; - # Redirect all HTTP traffic to HTTPS - return 301 https://$host$request_uri; + location /ping { + if ($host = "localhost") { + return 301 https://$host:8443$request_uri; + } + } + + location / { + # Default redirect for all other cases + return 301 https://$host$request_uri; + } } # HTTPS configuration diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 30ed2fa8..97057235 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -1,5 +1,5 @@ #!/command/with-contenv sh -response=$(curl --location --insecure --silent http://localhost/ping) +response=$(curl --location --insecure --silent http://localhost:8080/ping) if [ "$response" = "pong" ]; then exit 0 diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index 4c77f1e4..475cc799 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -82,4 +82,6 @@ EXPOSE 9000 WORKDIR ${APP_BASE_DIR} +USER www-data + CMD ["php-fpm"] \ No newline at end of file From d3f7e070b5dbf570dd8af463aa605293ca0fddb3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 12:20:10 -0500 Subject: [PATCH 04/48] Improved readability of health check --- .../fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check | 5 ++--- .../fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index 97057235..73a79e7f 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -1,10 +1,9 @@ #!/command/with-contenv sh -response=$(curl --location --insecure --silent http://localhost:8080/ping) +response=$(curl --location --insecure --silent --show-error http://localhost:8080/ping) if [ "$response" = "pong" ]; then exit 0 else - echo "❌ There seems to be a failure in checking the web server + PHP-FPM. Here's the response:" - echo $response + echo "❌ There seems to be a failure in checking the web server + PHP-FPM." exit 1 fi \ No newline at end of file diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 97057235..73a79e7f 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -1,10 +1,9 @@ #!/command/with-contenv sh -response=$(curl --location --insecure --silent http://localhost:8080/ping) +response=$(curl --location --insecure --silent --show-error http://localhost:8080/ping) if [ "$response" = "pong" ]; then exit 0 else - echo "❌ There seems to be a failure in checking the web server + PHP-FPM. Here's the response:" - echo $response + echo "❌ There seems to be a failure in checking the web server + PHP-FPM." exit 1 fi \ No newline at end of file From 5e0dd09d76cd73e2fa839703f0d2130afd8a5da4 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 13:30:51 -0500 Subject: [PATCH 05/48] Improved health check --- .../etc/s6-overlay/s6-rc.d/nginx/data/check | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index 73a79e7f..ec275ce9 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -1,5 +1,15 @@ #!/command/with-contenv sh -response=$(curl --location --insecure --silent --show-error http://localhost:8080/ping) +if [ "$SSL_MODE" = "off" ]; then + curl_options="--location --silent --show-error" + http_port=8080 + http_scheme=http +else + curl_options="--location --insecure --silent --show-error" + http_port=8443 + http_scheme=https +fi + +response=$(curl $curl_options $http_scheme://localhost:$http_port/ping) if [ "$response" = "pong" ]; then exit 0 From 85e3d976cac2ce6dcffcf8cf21b51a282197aabe Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 13:31:03 -0500 Subject: [PATCH 06/48] Simplified NGINX config --- .../fpm-nginx/etc/nginx/sites-available/ssl-full | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full index bd409da8..b02ed186 100644 --- a/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full +++ b/src/variations/fpm-nginx/etc/nginx/sites-available/ssl-full @@ -6,16 +6,8 @@ server { server_name _; - location /ping { - if ($host = "localhost") { - return 301 https://$host:8443$request_uri; - } - } - - location / { - # Default redirect for all other cases - return 301 https://$host$request_uri; - } + # Redirect all HTTP traffic to HTTPS + return 301 https://$host$request_uri; } # HTTPS configuration From 52933562f10c01c4fc9b9595d1cb32794f693803 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 13:32:28 -0500 Subject: [PATCH 07/48] Set cert to not be a CA cert --- src/s6/etc/entrypoint.d/10-init-webserver-config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index e6c603f5..45ec01dc 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -139,7 +139,7 @@ validate_ssl(){ fi echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." - openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.gitpod.io,*.ngrok.io,*.nip.io" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 + openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.gitpod.io,*.ngrok.io,*.nip.io" -addext "basicConstraints=CA:FALSE" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 } ########## From 230f18ade18b8c42a21b4f1317b5d6797ec35010 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 13:32:45 -0500 Subject: [PATCH 08/48] Configured Apache directories for changing permissions --- .../local/bin/docker-php-serversideup-set-file-permissions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index cd116c32..063eb35e 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -70,7 +70,7 @@ case "$OS" in debian) case "$SERVICE" in apache) - DIRS="/var/www/html /etc/apache2/sites-available" + DIRS="/etc/apache2 /etc/ssl/private /var/log/apache2" ;; nginx) DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/" @@ -84,7 +84,7 @@ case "$OS" in alpine) case "$SERVICE" in apache) - DIRS="/var/www/localhost/htdocs /etc/apache2" + DIRS="/etc/apache2 /etc/ssl/private" ;; nginx) DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/lib/nginx" From 50c50e8277174690c9c260b7679b8722c777ffd3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 13:39:44 -0500 Subject: [PATCH 09/48] Changed Apache to run as unprivileged --- src/variations/fpm-apache/Dockerfile | 6 +++++- src/variations/fpm-apache/etc/apache2/ports.conf | 9 +++++++++ .../etc/apache2/sites-available/ssl-full.conf | 4 ++-- .../etc/apache2/sites-available/ssl-mixed.conf | 4 ++-- .../etc/apache2/sites-available/ssl-off.conf | 2 +- .../etc/apache2/vhost-templates/https.conf | 2 ++ .../etc/s6-overlay/s6-rc.d/apache2/data/check | 12 +++++++++++- 7 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 src/variations/fpm-apache/etc/apache2/ports.conf diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 7dbaceac..ea753cb6 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -136,7 +136,11 @@ COPY --chmod=755 src/php-fpm.d/ / # install composer from Composer's official Docker image COPY --from=composer:2 /usr/bin/composer /usr/bin/composer -EXPOSE 80 443 +RUN docker-php-serversideup-set-file-permissions --owner www-data:www-data --service apache + +USER www-data + +EXPOSE 8080 8443 WORKDIR ${APP_BASE_DIR} diff --git a/src/variations/fpm-apache/etc/apache2/ports.conf b/src/variations/fpm-apache/etc/apache2/ports.conf new file mode 100644 index 00000000..5284a27e --- /dev/null +++ b/src/variations/fpm-apache/etc/apache2/ports.conf @@ -0,0 +1,9 @@ +Listen 8080 + + + Listen 8443 + + + + Listen 8443 + \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf index 976119e8..16b754f6 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-full.conf @@ -1,4 +1,4 @@ - + # Configure ServerAdmin and ServerName ServerName localhost ServerAdmin webmaster@localhost @@ -18,6 +18,6 @@ - + Include /etc/apache2/vhost-templates/https.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf index 355407c4..b3d46bfa 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-mixed.conf @@ -1,7 +1,7 @@ - + Include /etc/apache2/vhost-templates/http.conf - + Include /etc/apache2/vhost-templates/https.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf index ce90f6ce..81c73ca0 100644 --- a/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf +++ b/src/variations/fpm-apache/etc/apache2/sites-available/ssl-off.conf @@ -1,3 +1,3 @@ - + Include /etc/apache2/vhost-templates/http.conf \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf b/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf index 3a2643f5..2cec6c24 100644 --- a/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf +++ b/src/variations/fpm-apache/etc/apache2/vhost-templates/https.conf @@ -5,6 +5,8 @@ ServerAdmin webmaster@localhost # Enable HTTP2 Protocols h2 http/1.1 +SSLProtocol -all +TLSv1.2 +TLSv1.3 + # Set CloudFlare Real IP RemoteIPHeader CF-Connecting-IP diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index 73a79e7f..ec275ce9 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -1,5 +1,15 @@ #!/command/with-contenv sh -response=$(curl --location --insecure --silent --show-error http://localhost:8080/ping) +if [ "$SSL_MODE" = "off" ]; then + curl_options="--location --silent --show-error" + http_port=8080 + http_scheme=http +else + curl_options="--location --insecure --silent --show-error" + http_port=8443 + http_scheme=https +fi + +response=$(curl $curl_options $http_scheme://localhost:$http_port/ping) if [ "$response" = "pong" ]; then exit 0 From bcba3d137d38e2634c8d3e0a4234846a81ebe13d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 14:44:24 -0500 Subject: [PATCH 10/48] Converted NGINX Unit to be unprivileged --- .../docker-php-serversideup-set-file-permissions | 3 +++ src/variations/unit/Dockerfile | 15 +++++++++++---- .../unit/etc/entrypoint.d/10-init-unit.sh | 6 +++--- .../unit/etc/unit/config.d/ssl-full.json.template | 4 ++-- .../etc/unit/config.d/ssl-mixed.json.template | 4 ++-- .../unit/etc/unit/config.d/ssl-off.json.template | 2 +- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index 063eb35e..e048fe50 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -75,6 +75,9 @@ case "$OS" in nginx) DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/" ;; + unit) + DIRS="/var/log/unit /var/run/unit /etc/unit /etc/ssl/private/ /var/lib/unit/" + ;; *) echo "$script_name: Unsupported service: $SERVICE" exit 1 diff --git a/src/variations/unit/Dockerfile b/src/variations/unit/Dockerfile index de8378b6..2357e3ab 100644 --- a/src/variations/unit/Dockerfile +++ b/src/variations/unit/Dockerfile @@ -28,14 +28,12 @@ RUN set -ex && \ LD_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_LDFLAGS_MAINT_APPEND="-Wl,--as-needed -pie" dpkg-buildflags --get LDFLAGS)" && \ CONFIGURE_ARGS_MODULES="--prefix=/usr \ --statedir=/var/lib/unit \ - --control=unix:/var/run/control.unit.sock \ + --control=unix:/var/run/unit/control.unit.sock \ --runstatedir=/var/run \ - --pid=/var/run/unit.pid \ + --pid=/var/run/unit/unit.pid \ --logdir=/var/log \ --log=/dev/stdout \ --tmpdir=/var/tmp \ - --user=www-data \ - --group=www-data \ --openssl \ --libdir=/usr/lib/$DEB_HOST_MULTIARCH" && \ CONFIGURE_ARGS="$CONFIGURE_ARGS_MODULES --njs" && \ @@ -135,6 +133,9 @@ RUN docker-php-serversideup-dep-install-alpine "${DEPENDENCY_PACKAGES_ALPINE}" & mkdir -p "${COMPOSER_HOME}" && \ chown -R www-data:www-data "${COMPOSER_HOME}" && \ \ + # Create run directory for unit + mkdir -p /var/run/unit && \ + \ # Install default PHP extensions install-php-extensions "${DEPENDENCY_PHP_EXTENSIONS}" @@ -146,4 +147,10 @@ STOPSIGNAL SIGTERM WORKDIR ${APP_BASE_DIR} +RUN docker-php-serversideup-set-file-permissions --owner www-data:www-data --service unit + +USER www-data + +EXPOSE 8080 8443 + CMD ["unitd", "--no-daemon"] \ No newline at end of file diff --git a/src/variations/unit/etc/entrypoint.d/10-init-unit.sh b/src/variations/unit/etc/entrypoint.d/10-init-unit.sh index 361ba2e3..8ca16b8f 100644 --- a/src/variations/unit/etc/entrypoint.d/10-init-unit.sh +++ b/src/variations/unit/etc/entrypoint.d/10-init-unit.sh @@ -13,7 +13,7 @@ WAITLOOPS=5 SLEEPSEC=1 UNIT_CONFIG_DIRECTORY=${UNIT_CONFIG_DIRECTORY:-"/etc/unit/config.d"} UNIT_CONFIG_FILE=${UNIT_CONFIG_FILE:-"$UNIT_CONFIG_DIRECTORY/config.json"} -UNIT_SOCKET_LOCATION=${UNIT_SOCKET_LOCATION:-"/var/run/control.unit.sock"} +UNIT_SOCKET_LOCATION=${UNIT_SOCKET_LOCATION:-"/var/run/unit/control.unit.sock"} ########## # Functions @@ -133,7 +133,7 @@ configure_unit() { curl_put "-d" '"/dev/stdout"' "config/access_log" echo "$script_name: Stopping Unit daemon after initial configuration..." - kill -TERM "$(/bin/cat /var/run/unit.pid)" + kill -TERM "$(/bin/cat /var/run/unit/unit.pid)" for i in $(/usr/bin/seq $WAITLOOPS); do if [ -S "$UNIT_SOCKET_LOCATION" ]; then @@ -144,7 +144,7 @@ configure_unit() { fi done if [ -S "$UNIT_SOCKET_LOCATION" ]; then - kill -KILL "$(/bin/cat /var/run/unit.pid)" + kill -KILL "$(/bin/cat /var/run/unit/unit.pid)" rm -f "$UNIT_SOCKET_LOCATION" fi diff --git a/src/variations/unit/etc/unit/config.d/ssl-full.json.template b/src/variations/unit/etc/unit/config.d/ssl-full.json.template index a9473404..40ee5dff 100644 --- a/src/variations/unit/etc/unit/config.d/ssl-full.json.template +++ b/src/variations/unit/etc/unit/config.d/ssl-full.json.template @@ -1,6 +1,6 @@ { "listeners": { - "*:80": { + "*:8080": { "pass": "routes/http-redirect", "forwarded": { "client_ip": "CF-Connecting-IP", @@ -31,7 +31,7 @@ ] } }, - "*:443": { + "*:8443": { "pass": "routes/app", "tls": { "certificate": "${UNIT_CERTIFICATE_NAME}", diff --git a/src/variations/unit/etc/unit/config.d/ssl-mixed.json.template b/src/variations/unit/etc/unit/config.d/ssl-mixed.json.template index 91b41517..4699a9a5 100644 --- a/src/variations/unit/etc/unit/config.d/ssl-mixed.json.template +++ b/src/variations/unit/etc/unit/config.d/ssl-mixed.json.template @@ -1,6 +1,6 @@ { "listeners": { - "*:80": { + "*:8080": { "pass": "routes", "forwarded": { "client_ip": "CF-Connecting-IP", @@ -31,7 +31,7 @@ ] } }, - "*:443": { + "*:8443": { "pass": "routes", "tls": { "certificate": "${UNIT_CERTIFICATE_NAME}", diff --git a/src/variations/unit/etc/unit/config.d/ssl-off.json.template b/src/variations/unit/etc/unit/config.d/ssl-off.json.template index 78935046..c94ddfed 100644 --- a/src/variations/unit/etc/unit/config.d/ssl-off.json.template +++ b/src/variations/unit/etc/unit/config.d/ssl-off.json.template @@ -1,6 +1,6 @@ { "listeners": { - "*:80": { + "*:8080": { "pass": "routes", "forwarded": { "client_ip": "CF-Connecting-IP", From 9979e347fbb6619c3d008214b167760ddea445b9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Tue, 16 Apr 2024 14:46:28 -0500 Subject: [PATCH 11/48] Dropped permissions on CLI image --- src/variations/cli/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/variations/cli/Dockerfile b/src/variations/cli/Dockerfile index a263cbb5..d4803da4 100644 --- a/src/variations/cli/Dockerfile +++ b/src/variations/cli/Dockerfile @@ -66,4 +66,6 @@ COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR ${APP_BASE_DIR} +USER www-data + ENTRYPOINT ["docker-php-serversideup-entrypoint"] From afb81a9d1ffdaaa0256bd7aa9aa2dd35bb4e9718 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 17 Apr 2024 11:46:04 -0500 Subject: [PATCH 12/48] Improve UX with better healthcheck notifications --- .../fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check | 3 ++- .../fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check index ec275ce9..0375f489 100644 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check +++ b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/data/check @@ -12,8 +12,9 @@ fi response=$(curl $curl_options $http_scheme://localhost:$http_port/ping) if [ "$response" = "pong" ]; then + echo "✅ Apache + PHP-FPM is running correctly." exit 0 else - echo "❌ There seems to be a failure in checking the web server + PHP-FPM." + echo "❌ There seems to be a failure in checking Apache + PHP-FPM. Trying again..." exit 1 fi \ No newline at end of file diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check index ec275ce9..d02ced1f 100644 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check +++ b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/data/check @@ -12,8 +12,9 @@ fi response=$(curl $curl_options $http_scheme://localhost:$http_port/ping) if [ "$response" = "pong" ]; then + echo "✅ NGINX + PHP-FPM is running correctly." exit 0 else - echo "❌ There seems to be a failure in checking the web server + PHP-FPM." + echo "❌ There seems to be a failure in checking the NGINX + PHP-FPM. Trying again..." exit 1 fi \ No newline at end of file From ef507728e582804be2cb5a184125623cbe652f12 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 17 Apr 2024 13:48:07 -0500 Subject: [PATCH 13/48] Added empty line for easier expansion by scripts --- .../usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf index 33018e49..6a32aea2 100644 --- a/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf +++ b/src/php-fpm.d/usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf @@ -274,4 +274,4 @@ php_value[max_execution_time] = ${PHP_MAX_EXECUTION_TIME} ; Upload settings php_value[post_max_size] = ${PHP_POST_MAX_SIZE} -php_value[upload_max_filesize] = ${PHP_UPLOAD_MAX_FILE_SIZE} \ No newline at end of file +php_value[upload_max_filesize] = ${PHP_UPLOAD_MAX_FILE_SIZE} From d08b6a9fb8ad26b6f2f47565781203b9fac4e8d0 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 07:43:12 -0500 Subject: [PATCH 14/48] Refactor .env.example file in docs directory --- docs/.env.example | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/.env.example b/docs/.env.example index e2fe96ab..ecaca8e8 100644 --- a/docs/.env.example +++ b/docs/.env.example @@ -1,7 +1,4 @@ NUXT_APP_BASE_URL=/open-source/docker-php TOP_LEVEL_DOMAIN=http://localhost:3000 BASE_PATH=http://localhost:3000/open-source/docker-php -TOP_LEVEL_DOMAIN=http://localhost:3000 -ALGOLIA_API_KEY=changeme -ALGOLIA_APPLICATION_ID=changeme -ALGOLIA_INDEX=changeme \ No newline at end of file +TOP_LEVEL_DOMAIN=http://localhost:3000 \ No newline at end of file From 0a37ba640fd06f6ddfa770dabcf055e015ae5c75 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 07:45:38 -0500 Subject: [PATCH 15/48] Remove beta notice --- docs/content/docs/1.index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/content/docs/1.index.md b/docs/content/docs/1.index.md index ed009014..e1fa7b97 100644 --- a/docs/content/docs/1.index.md +++ b/docs/content/docs/1.index.md @@ -4,10 +4,6 @@ head.title: 'Introduction - Docker Images - Server Side Up' layout: docs --- -::note -These docs refer to the latest [**v3 Beta**](https://github.com/serversideup/docker-php/discussions/254), which are expected to hit "stable" soon. If you are looking for the v2 docs, you can find them [here](https://github.com/serversideup/docker-php/tree/v2.x/docs/content/docs). -:: - # Introduction From 11c68267c873b61a79a40c0180bcc4a4c14b0725 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 07:47:59 -0500 Subject: [PATCH 16/48] Add unprivileged by default support for Docker images --- .../docs/2.getting-started/1.these-images-vs-others.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/content/docs/2.getting-started/1.these-images-vs-others.md b/docs/content/docs/2.getting-started/1.these-images-vs-others.md index 303f4740..d46afdb6 100644 --- a/docs/content/docs/2.getting-started/1.these-images-vs-others.md +++ b/docs/content/docs/2.getting-started/1.these-images-vs-others.md @@ -14,6 +14,7 @@ layout: docs | Multi-arch support | ✅ | ✅ | | Init System | Docker CMD | Docker CMD or [S6-Overlay](https://github.com/just-containers/s6-overlay) | | Published Registry| DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php), [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) | +| Unprivileged by default | ❌ | ✅ | | Variable-first configuration | ❌ | ✅ | | Includes `composer` | ❌ | ✅ | | Includes [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer) | ❌ | ✅ | @@ -24,6 +25,11 @@ layout: docs | NGINX Unit variation| ❌ | ✅ | | Native health checks | ❌ | ✅ | +## Unprivileged by Default +We believe in the principle of least privilege. Our images run as an unprivileged user by default. This means that if your application is compromised, the attacker will have a harder time escalating their privileges to the root user. + +Running unprivileged images also improves compatibility of running your containers in a Kubernetes environment, where running as root is not allowed. + ## Variable-first Configuration Our design philosophy is built all around simplicity. The process of customizing the behavior of PHP is as simple as setting an environment variable. We took every common configuration option and set it up so you can change these values in a simple method, defaulting every single option to production-ready values. From bca5cb53efd4268623e65e6b8eb42107964912dd Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 07:59:44 -0500 Subject: [PATCH 17/48] Added hetzner --- docs/content/docs/2.getting-started/4.choosing-a-host.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/2.getting-started/4.choosing-a-host.md b/docs/content/docs/2.getting-started/4.choosing-a-host.md index df9377fa..acab7f65 100644 --- a/docs/content/docs/2.getting-started/4.choosing-a-host.md +++ b/docs/content/docs/2.getting-started/4.choosing-a-host.md @@ -11,8 +11,11 @@ We believe privacy and control is the #1 priority when it comes to hosting infra We run all our production servers on the latest LTS release of Ubuntu Server. The hosts we use are below. Some may be affiliate links that kick a few bucks at no extra cost to you, but they do not affect our recommendations at all. +### [Hetzner](https://hetzner.cloud/?ref=lhLUIrkdUPhl) +**Our current favorite.** Your mind will be blown for the specs you get for the prices. They are based in Europe, but have US datacenters too. We're running full out SaaS products for $5 USD a month. 🤯 + ### [Vultr](https://vultr.grsm.io/create) -**Our current favorite.** Excellent performance and value. Lots of datacenter options too. +Excellent performance and value. Lots of data center options too. ### [Digital Ocean](https://m.do.co/c/f3bad4b927ca) Lots of developer love here. Not the best performing servers, but they do have a lot of awesome products! From 3a6f3ee535ad721dfee41a0b00033824d8c09b38 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 08:19:01 -0500 Subject: [PATCH 18/48] Add default configurations for unprivileged Docker images --- .../3.default-configurations.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/content/docs/2.getting-started/3.default-configurations.md b/docs/content/docs/2.getting-started/3.default-configurations.md index 7c1bca6c..e3acb3e8 100644 --- a/docs/content/docs/2.getting-started/3.default-configurations.md +++ b/docs/content/docs/2.getting-started/3.default-configurations.md @@ -12,6 +12,37 @@ layout: docs ## Production-ready and optimized for Laravel & WordPress All values are defaulted to improve security and performance. We also spent the time to carefully review official documentation and include packages that are required specifically for Laravel and WordPress. +## Unprivileged by Default +All images default to running as the OS-native `www-data` user. + +::note +The `www-data` UID/GID is different between Debian (`33:33`) and Alpine (`82:82`). We left these values alone to make these images as native as possible. If you switch between Debian and Alpine, you may need to adjust file permissions in your Docker image and volume mounts. +:: + +Since these images are not privileged, that means they are not running on ports less than 1024: + +| **Variation** | **Default Ports** | +|---------------|-------------------| +| cli | (none) | +| fpm | 9000 | +| fpm-nginx | HTTP: 8080, HTTPS: 8443 | +| fpm-apache | HTTP: 8080, HTTPS: 8443 | +| unit | HTTP: 8080, HTTPS: 8443 | + +### How do I run these services on ports 80 and/or 443? +Almost everyone will want to run these services on ports 80 and 443. If you have an advanced setup, you can use a reverse proxy like Caddy or Traefik to handle the SSL termination and forward the traffic to the container on the non-privileged port. + +Or you can simply use Docker's port mapping feature to map the container port to the host port. For example, to run the `fpm-nginx` variation on port 80 and 443, you can run the following command: + +::code-panel +--- +label: Run FPM NGINX on port 80 and 443 +--- +```bash +docker run -p 80:8080 -p 443:8443 serversideup/php:8.3-fpm-nginx +``` +:: + ## Default Environment Variables We allow the ability to customize the behavior of PHP with environment variables. Be sure to review our production-ready default values on our [environment variable specification](/docs/reference/environment-variable-specification) page. From 5a2b80b8f5ffefa9fc0d53fbb588368cc67f15d5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 08:19:08 -0500 Subject: [PATCH 19/48] Update Dockerfile examples in upgrade guide --- .../docs/2.getting-started/3.upgrade-guide.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/2.getting-started/3.upgrade-guide.md b/docs/content/docs/2.getting-started/3.upgrade-guide.md index 220da510..80a63c9f 100644 --- a/docs/content/docs/2.getting-started/3.upgrade-guide.md +++ b/docs/content/docs/2.getting-started/3.upgrade-guide.md @@ -26,9 +26,9 @@ Any updates that you apply have a risk of breaking other things inside the conta ::code-panel --- -label: Example Dockerfile with manual updates +label: Example Dockerfile with manual updates for Debian --- -```txt +```dockerfile FROM serversideup/php:8.3.2-fpm-nginx RUN apt-get update \ @@ -38,6 +38,20 @@ RUN apt-get update \ ``` :: +If you're running an Alpine-based image, you can use the following commands: + +::code-panel +--- +label: Example Dockerfile with manual updates for Alpine +--- +```dockerfile +FROM serversideup/php:8.3.2-fpm-nginx-alpine + +RUN apk update \ + && apk upgrade \ + && rm -rf /var/cache/apk/* +``` +:: ## Subscribe to repository updates Regardless if you are choosing to use automatic updates or manual updates, it is highly advised to subscribe to our releases. You can do this through the "Watch" button on our [GitHub](https://github.com/serversideup/docker-php). From 5778ca1e4396045f10cbd4276c1d03139d33cd51 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 08:19:23 -0500 Subject: [PATCH 20/48] Improved syntax highlighting --- .../docs/3.guide/3.installing-additional-php-extensions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/3.guide/3.installing-additional-php-extensions.md b/docs/content/docs/3.guide/3.installing-additional-php-extensions.md index 7a0c5abd..1058cc8f 100644 --- a/docs/content/docs/3.guide/3.installing-additional-php-extensions.md +++ b/docs/content/docs/3.guide/3.installing-additional-php-extensions.md @@ -13,7 +13,7 @@ Installing extensions is easy. All you need to do is find the extension you'd li --- label: "Example: Installing the \"intl\" extension" --- -```txt +```dockerfile FROM serversideup/php:8.2.12-fpm-nginx-bookworm RUN install-php-extensions intl @@ -63,7 +63,7 @@ services: --- label: "New File: Dockerfile" --- -```txt +```dockerfile FROM serversideup/php:8.2.12-fpm-nginx-bookworm RUN install-php-extensions imagick From 1ac057480cbc72917840fc345cfe23ceebceeeda Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 08:31:20 -0500 Subject: [PATCH 21/48] Updated versions in README --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2f4fabc1..9ec95e80 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,11 @@ serversideup/php:8.2-fpm-nginx | ⚙️ Variation | 🚀 Version | | ------------ | ---------- | -| cli | [![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name)
[![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name)
[![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name)
[![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name) | -| fpm | [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name)
[![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name)
[![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name)
[![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name) | -| fpm-apache | [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name)
[![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name)
[![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name)
[![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) | -| fpm-nginx | [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name)
[![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name)
[![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name)
[![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name) | +| cli | [![serversideup/php:8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli?label=serversideup%2Fphp%3A8.3-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli&page=1&ordering=-name)[![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name)[![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name)[![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name)[![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name) | +| fpm | [![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm&page=1&ordering=-name)[![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name)[![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name)[![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name)[![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name) | +| fpm-apache | [![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache&page=1&ordering=-name)[![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name)[![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name)[![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name)[![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) | +| fpm-nginx | [![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx&page=1&ordering=-name)[![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name)[![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name)[![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name)[![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name) | +| unit | [![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name)[![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name)[![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name)[![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name)[![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name) | ### Real-life working example You can see a bigger picture on how these images are used from Development to Production by viewing this video that shows a high level overview how we deploy "[ROAST](https://roastandbrew.coffee/)" which is a demo production app for [our book](https://serversideup.net/ultimate-guide-to-building-apis-and-spas-with-laravel-and-vuejs/). From ce326111818a44a616d8b75ae711a02acf93cb2e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 09:07:45 -0500 Subject: [PATCH 22/48] Renamed provided PHP ini file per https://github.com/serversideup/docker-php/issues/242#issuecomment-1872570005 --- .../local/etc/php/{php.ini => conf.d/serversideup-docker-php.ini} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/common/usr/local/etc/php/{php.ini => conf.d/serversideup-docker-php.ini} (100%) diff --git a/src/common/usr/local/etc/php/php.ini b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini similarity index 100% rename from src/common/usr/local/etc/php/php.ini rename to src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini From b827e8c7fb403e9981e519db2c4ed7b021357f10 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 09:30:40 -0500 Subject: [PATCH 23/48] Documented PHP ini customizations. Fixes #242 --- .../3.default-configurations.md | 5 +++ .../1.changing-common-php-settings.md} | 39 ++++++++++++++++++- docs/nuxt.config.ts | 1 + 3 files changed, 43 insertions(+), 2 deletions(-) rename docs/content/docs/{3.guide/2.changing-common-php-settings.md => 3.customizing-the-image/1.changing-common-php-settings.md} (57%) diff --git a/docs/content/docs/2.getting-started/3.default-configurations.md b/docs/content/docs/2.getting-started/3.default-configurations.md index e3acb3e8..3411a741 100644 --- a/docs/content/docs/2.getting-started/3.default-configurations.md +++ b/docs/content/docs/2.getting-started/3.default-configurations.md @@ -46,6 +46,11 @@ docker run -p 80:8080 -p 443:8443 serversideup/php:8.3-fpm-nginx ## Default Environment Variables We allow the ability to customize the behavior of PHP with environment variables. Be sure to review our production-ready default values on our [environment variable specification](/docs/reference/environment-variable-specification) page. +## Default PHP INI Settings +We provide a default PHP ini that come with the suggested and hardened settings for running PHP in production. This file is located at `/usr/local/etc/php/conf.d/serversideup-docker-php.ini`. + +To customize the PHP ini settings, read our [Changing Common PHP Settings](/docs/customizing-the-image/changing-common-php-settings) guide. + ## Default PHP Extensions The following extensions are installed by default: diff --git a/docs/content/docs/3.guide/2.changing-common-php-settings.md b/docs/content/docs/3.customizing-the-image/1.changing-common-php-settings.md similarity index 57% rename from docs/content/docs/3.guide/2.changing-common-php-settings.md rename to docs/content/docs/3.customizing-the-image/1.changing-common-php-settings.md index 5a3870e4..1bffb1d6 100644 --- a/docs/content/docs/3.guide/2.changing-common-php-settings.md +++ b/docs/content/docs/3.customizing-the-image/1.changing-common-php-settings.md @@ -49,9 +49,44 @@ docker run -d \ :: ## Setting your own php.ini +PHP will read the `php.ini` file from the `/usr/local/etc/php/conf.d/` directory in alphabetical order. This means you can create your own `php.ini` file and mount it to the container to override the default settings. -::note -We're still working on documenting this section. Thanks for your patience. [Open a discussion on GitHub](https://github.com/serversideup/docker-php/discussions/new?category=general) if you'd like to see this added sooner. +For example, we can create this file in our project directory: +::code-panel +--- +label: "zzz-custom-php.ini" +--- +```ini +mysqli.max_persistent = 300 +opcache.max_file_size = 10M +opcache.log_verbosity_level = 3 +``` +:: + +The in our Dockerfile, we can copy this file to the `/usr/local/etc/php/conf.d/` directory: +::code-panel +--- +label: "Dockerfile: Append to our default configuration" +--- +```dockerfile +FROM serversideup/php:8.3-fpm-nginx-bookworm + +COPY zzz-custom-php.ini /usr/local/etc/php/conf.d/ +``` +:: + +If you prefer to remove the default `php.ini` file, you can do so by adding the following line to your Dockerfile: + +::code-panel +--- +label: "Dockerfile: Remove our default configuration" +--- +```dockerfile +FROM serversideup/php:8.3-fpm-nginx-bookworm + +RUN rm /usr/local/etc/php/conf.d/serversideup-docker-php.ini +COPY zzz-custom-php.ini /usr/local/etc/php/conf.d/ +``` :: ## Validating changes diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 08993fe7..acecb5b3 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -40,6 +40,7 @@ export default defineNuxtConfig({ preload: [ 'dockerfile', + 'ini', 'php' ] }, From d1e421852a888b156c91cae616e0c313b60f59aa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 09:41:47 -0500 Subject: [PATCH 24/48] Restructured the docs --- ...ting-from-v2-to-v3.md => 100.migrating-from-v2-to-v3.md} | 0 .../{6.using-s6-overlay.md => 2.using-s6-overlay.md} | 0 .../3.using-wordpress-with-docker.md} | 6 +++--- .../1.changing-common-php-settings.md | 0 .../2.installing-additional-php-extensions.md} | 2 -- .../3.adding-your-own-start-up-scripts.md} | 0 .../4.configuring-ssl.md} | 0 .../1.environment-variable-specification.md | 0 .../{6.reference => 7.reference}/2.command-reference.md | 0 9 files changed, 3 insertions(+), 5 deletions(-) rename docs/content/docs/3.guide/{7.migrating-from-v2-to-v3.md => 100.migrating-from-v2-to-v3.md} (100%) rename docs/content/docs/3.guide/{6.using-s6-overlay.md => 2.using-s6-overlay.md} (100%) rename docs/content/docs/{5.wordpress/1.wordpress-optimizations.md => 3.guide/3.using-wordpress-with-docker.md} (94%) rename docs/content/docs/{3.customizing-the-image => 5.customizing-the-image}/1.changing-common-php-settings.md (100%) rename docs/content/docs/{3.guide/3.installing-additional-php-extensions.md => 5.customizing-the-image/2.installing-additional-php-extensions.md} (99%) rename docs/content/docs/{3.guide/4.adding-your-own-start-up-scripts.md => 5.customizing-the-image/3.adding-your-own-start-up-scripts.md} (100%) rename docs/content/docs/{3.guide/5.configuring-ssl.md => 5.customizing-the-image/4.configuring-ssl.md} (100%) rename docs/content/docs/{6.reference => 7.reference}/1.environment-variable-specification.md (100%) rename docs/content/docs/{6.reference => 7.reference}/2.command-reference.md (100%) diff --git a/docs/content/docs/3.guide/7.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md similarity index 100% rename from docs/content/docs/3.guide/7.migrating-from-v2-to-v3.md rename to docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md diff --git a/docs/content/docs/3.guide/6.using-s6-overlay.md b/docs/content/docs/3.guide/2.using-s6-overlay.md similarity index 100% rename from docs/content/docs/3.guide/6.using-s6-overlay.md rename to docs/content/docs/3.guide/2.using-s6-overlay.md diff --git a/docs/content/docs/5.wordpress/1.wordpress-optimizations.md b/docs/content/docs/3.guide/3.using-wordpress-with-docker.md similarity index 94% rename from docs/content/docs/5.wordpress/1.wordpress-optimizations.md rename to docs/content/docs/3.guide/3.using-wordpress-with-docker.md index e6438696..db3abfc4 100644 --- a/docs/content/docs/5.wordpress/1.wordpress-optimizations.md +++ b/docs/content/docs/3.guide/3.using-wordpress-with-docker.md @@ -4,18 +4,18 @@ description: 'Learn how serverideup/php is optimized for running WordPress in pr layout: docs --- -# WordPress Optimizations +# Using WordPress with Docker Ready to take the dive and modernize WordPress with Docker? Long story short, it can be definitely be worth it depending on your use case. From our experience, you can save yourself hours, or really over-engineer and dig yourself a hole. -## 🔑 WordPress & Security Optimizations +## WordPress & Security Optimizations * Hardening of Apache & NGINX included * Disabling of XML-RPC * Preventative access to sensitive version control or CI files * Protection against other common attacks -See our [Apache security.conf](https://github.com/serversideup/docker-php/blob/main/src/fpm-apache/etc/apache2/conf-available/security.conf) for more detail. +See our [Apache security.conf](https://github.com/serversideup/docker-php/blob/main/src/variations/fpm-apache/etc/apache2/conf-available/security.conf) for more detail. ## "The WordPress Way" We're very grateful for WordPress, and it even runs our blog for [Server Side Up](https://serversideup.net/). The only downside with WordPress is it has a lot of history with it. That means the core was written with in a mindset before all of the fancy and flashy methods that we have today. diff --git a/docs/content/docs/3.customizing-the-image/1.changing-common-php-settings.md b/docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md similarity index 100% rename from docs/content/docs/3.customizing-the-image/1.changing-common-php-settings.md rename to docs/content/docs/5.customizing-the-image/1.changing-common-php-settings.md diff --git a/docs/content/docs/3.guide/3.installing-additional-php-extensions.md b/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md similarity index 99% rename from docs/content/docs/3.guide/3.installing-additional-php-extensions.md rename to docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md index 1058cc8f..a9a49e58 100644 --- a/docs/content/docs/3.guide/3.installing-additional-php-extensions.md +++ b/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md @@ -34,7 +34,6 @@ If you're using Docker Compose, you'll want to make sure that you're not calling label: Original docker-compose.yml file --- ```yaml -version: '3.7' services: php: image: serversideup/php:8.2.12-fpm-nginx-bookworm @@ -48,7 +47,6 @@ services: label: "Updated docker-compose.yml" --- ```yaml -version: '3.7' services: php: build: diff --git a/docs/content/docs/3.guide/4.adding-your-own-start-up-scripts.md b/docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md similarity index 100% rename from docs/content/docs/3.guide/4.adding-your-own-start-up-scripts.md rename to docs/content/docs/5.customizing-the-image/3.adding-your-own-start-up-scripts.md diff --git a/docs/content/docs/3.guide/5.configuring-ssl.md b/docs/content/docs/5.customizing-the-image/4.configuring-ssl.md similarity index 100% rename from docs/content/docs/3.guide/5.configuring-ssl.md rename to docs/content/docs/5.customizing-the-image/4.configuring-ssl.md diff --git a/docs/content/docs/6.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md similarity index 100% rename from docs/content/docs/6.reference/1.environment-variable-specification.md rename to docs/content/docs/7.reference/1.environment-variable-specification.md diff --git a/docs/content/docs/6.reference/2.command-reference.md b/docs/content/docs/7.reference/2.command-reference.md similarity index 100% rename from docs/content/docs/6.reference/2.command-reference.md rename to docs/content/docs/7.reference/2.command-reference.md From b0bbf701c2f3a16f2ff53b0482f24738c3d4651c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 09:47:01 -0500 Subject: [PATCH 25/48] Update redirect paths in DocumentDrivenNotFound.vue --- docs/components/DocumentDrivenNotFound.vue | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/components/DocumentDrivenNotFound.vue b/docs/components/DocumentDrivenNotFound.vue index f96412ff..cef8d7f6 100644 --- a/docs/components/DocumentDrivenNotFound.vue +++ b/docs/components/DocumentDrivenNotFound.vue @@ -12,19 +12,16 @@ switch( redirectPath ){ navigateTo( redirectPath+'/these-images-vs-others', { replace: true } ); break; case '/docs/guide': - navigateTo( redirectPath+'/choosing-the-right-image', { replace: true } ); + navigateTo( redirectPath+'/migrating-from-official-php-images', { replace: true } ); break; case '/docs/reference': navigateTo( redirectPath+'/environment-variable-specification', { replace: true } ); break; case '/docs/laravel': - navigateTo( redirectPath+'/laravel-autorun-script', { replace: true } ); + navigateTo( redirectPath+'/laravel-automations', { replace: true } ); break; - case '/docs/wordpress': - navigateTo( redirectPath+'/wordpress-optimizations', { replace: true } ); - break; - case '/docs/reference': - navigateTo( redirectPath+'/environment-variable-specification', { replace: true } ); + case '/docs/customizing-the-image': + navigateTo( redirectPath+'/changing-common-php-settings', { replace: true } ); break; } \ No newline at end of file From 68f75ca018e7e91fe9293072918fea28fa0813f6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:06:00 -0500 Subject: [PATCH 26/48] Updated docs on Laravel Automations script --- .../docs/4.laravel/1.laravel-automations.md | 47 +++++++++++++++++++ .../4.laravel/1.laravel-autorun-script.md | 33 ------------- 2 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 docs/content/docs/4.laravel/1.laravel-automations.md delete mode 100644 docs/content/docs/4.laravel/1.laravel-autorun-script.md diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md new file mode 100644 index 00000000..527ad6d3 --- /dev/null +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -0,0 +1,47 @@ +--- +head.title: 'Laravel Automations Script - Docker PHP - Server Side Up' +description: 'Automate your deployments and minimize your efforts with Laravel.' +layout: docs +--- + +# Laravel Automations +`serversideup/php` has a "Laravel Automations" script that helps you automate certain steps during your deployments. By default, the script is **DISABLED**. We only recommend enabling this script in production environments. + +::note +`AUTORUN_ENABLED` must be set to `true` to enable the script. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. +:: + +## What the script does +This script executes on container start up and performs the following tasks: + +### php artisan migrate +Before running migrations, we ensure the database is online and ready to accept connections. By default, we will wait 30 seconds before timing out. + +You can enable the [`--isolated`](https://laravel.com/docs/11.x/migrations#running-migrations) flag by setting `AUTORUN_LARAVEL_MIGRATION_ISOLATION` to `true`, which will ensure no other containers are running a migration. + +**Special Notes for Isolated Migrations:** +- Requires Laravel v9.38.0+ +- Your database must support database locks (meaning SQLite is not supported) + +### php artisan storage:link +This command creates a symbolic link from `public/storage` to `storage/app/public`. + +### php artisan config:cache +This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. + +[Read more about configuration caching](https://laravel.com/docs/11.x/configuration#configuration-caching) + +### php artisan route:cache +This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. + +[Read more about route caching](https://laravel.com/docs/11.x/routing#route-caching) + +### php artisan view:cache +This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. + +[Read more about view caching](https://laravel.com/docs/11.x/views#optimizing-views) + +### php artisan event:cache +This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. + +[Read more about event caching](https://laravel.com/docs/11.x/events#event-discovery-in-production) \ No newline at end of file diff --git a/docs/content/docs/4.laravel/1.laravel-autorun-script.md b/docs/content/docs/4.laravel/1.laravel-autorun-script.md deleted file mode 100644 index 958dcab9..00000000 --- a/docs/content/docs/4.laravel/1.laravel-autorun-script.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -head.title: 'Laravel Autorun Script - Docker PHP - Server Side Up' -description: 'Automate your deployments and minimize your efforts with Laravel.' -layout: docs ---- - -# Laravel Autorun Script -`serversideup/php` has a "Laravel Autorun" script that helps you automate certain steps during your deployments. By default, storage linking is the only automation enabled. You can disable the entire script by setting setting the `AUTORUN_ENABLED` variable. See our [variable reference document](/docs/reference/environment-variable-specification) for more details. - -## Available Automations - -::code-panel ---- -label: Storage Linking ---- -```bash -php artisan storage:link -``` -:: - -**Storage linking is ENABLED by default.** You can disable this behavior by setting `AUTORUN_LARAVEL_STORAGE_LINK=false`. - -::code-panel ---- -label: Database Migrations ---- -```bash -php artisan migrate --force --isolated -``` -:: - -**Automatic migrations are DISABLED by default.** To enable, set an environment variable of `AUTORUN_LARAVEL_MIGRATION=true` on your container. This requires Laravel v9.38.0 or higher. - From 28858d56bcc8267b7067c494a97c3b35644bf385 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:10:22 -0500 Subject: [PATCH 27/48] Update Laravel task scheduler documentation and Docker Compose example --- .../content/docs/4.laravel/2.laravel-task-scheduler.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md index 4051d077..70d25fc3 100644 --- a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md +++ b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md @@ -13,17 +13,19 @@ Running a Laravel task scheduler with Docker can be a little different from the 1. The actual time trigger itself is set within Laravel ## More detail -We need to run the [schedule:work](https://laravel.com/docs/10.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/10.x/scheduling#scheduling-artisan-commands). +We need to run the [schedule:work](https://laravel.com/docs/11.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/11.x/scheduling#scheduling-artisan-commands). ## Examples +::note +You can do cool things with `PHP_FPM_POOL_NAME` to separate the task from the main PHP pool. This is helpful when debugging or monitoring the task. +:: ::code-panel --- label: Example Docker Compose File --- ```yaml -version: '3' services: php: image: my/laravel-app @@ -32,9 +34,7 @@ services: task: image: my/laravel-app - # Switch to "webuser" before running `php artisan` - # Declare command in list manner for environment variable expansion - command: ["su", "webuser", "-c", "php artisan schedule:work"] + command: ["php", "/var/www/html/artisan", "schedule:work"] environment: PHP_FPM_POOL_NAME: "my-app_task" ``` From ef668cc6446d528783950c0f44a7e2812eb43b2c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:12:30 -0500 Subject: [PATCH 28/48] Update Laravel task scheduler documentation and Docker Compose example --- docs/content/docs/4.laravel/2.laravel-task-scheduler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md index 70d25fc3..9727b784 100644 --- a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md +++ b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md @@ -23,7 +23,7 @@ You can do cool things with `PHP_FPM_POOL_NAME` to separate the task from the ma ::code-panel --- -label: Example Docker Compose File +label: Example & Simplified Docker Compose File --- ```yaml services: From da3b67ecabe438100190bb73ebbfda9dd3b83d5d Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:17:19 -0500 Subject: [PATCH 29/48] Update links in Laravel Automations documentation --- docs/content/docs/4.laravel/1.laravel-automations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/4.laravel/1.laravel-automations.md b/docs/content/docs/4.laravel/1.laravel-automations.md index 527ad6d3..f0431c72 100644 --- a/docs/content/docs/4.laravel/1.laravel-automations.md +++ b/docs/content/docs/4.laravel/1.laravel-automations.md @@ -29,19 +29,19 @@ This command creates a symbolic link from `public/storage` to `storage/app/publi ### php artisan config:cache This command caches all configuration files into a single file, which can then be quickly loaded by Laravel. Once the configuration is cache, the `.env` file will no longer be loaded. -[Read more about configuration caching](https://laravel.com/docs/11.x/configuration#configuration-caching) +[Read more about configuration caching →](https://laravel.com/docs/11.x/configuration#configuration-caching) ### php artisan route:cache This command caches the routes, dramatically decrease the time it takes to register all of your application's routes. After running this command, your cached routes file will be loaded on every request. -[Read more about route caching](https://laravel.com/docs/11.x/routing#route-caching) +[Read more about route caching →](https://laravel.com/docs/11.x/routing#route-caching) ### php artisan view:cache This command caches all of the views in your application, which can greatly decrease the time it takes to render your views. -[Read more about view caching](https://laravel.com/docs/11.x/views#optimizing-views) +[Read more about view caching →](https://laravel.com/docs/11.x/views#optimizing-views) ### php artisan event:cache This command creates a manifest of all your application's events and listeners, which can greatly speed up the process of registering them with Laravel. -[Read more about event caching](https://laravel.com/docs/11.x/events#event-discovery-in-production) \ No newline at end of file +[Read more about event caching →](https://laravel.com/docs/11.x/events#event-discovery-in-production) \ No newline at end of file From c42d6a1ec47e1cb9edfff8e764cd8cd0fbe2291b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:18:53 -0500 Subject: [PATCH 30/48] Update Laravel task scheduler documentation and Docker Compose example --- docs/content/docs/4.laravel/2.laravel-task-scheduler.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md index 9727b784..62c45adf 100644 --- a/docs/content/docs/4.laravel/2.laravel-task-scheduler.md +++ b/docs/content/docs/4.laravel/2.laravel-task-scheduler.md @@ -11,16 +11,14 @@ Running a Laravel task scheduler with Docker can be a little different from the 1. We will **not** use `cron` to run the scheduler 1. By default `schedule:work` checks every minute, so we will use that to run the system process 1. The actual time trigger itself is set within Laravel +1. You can do cool things with `PHP_FPM_POOL_NAME` to separate the task from the main PHP pool. This is helpful when debugging or monitoring the task. ## More detail We need to run the [schedule:work](https://laravel.com/docs/11.x/scheduling#running-the-scheduler-locally) command from Laravel. Although the docs say "Running the scheduler locally", this is what we want in production. It will run the scheduler in the foreground and execute it every minute. You can configure your Laravel app for the exact time that a command should run through a [scheduled task](https://laravel.com/docs/11.x/scheduling#scheduling-artisan-commands). ## Examples -::note -You can do cool things with `PHP_FPM_POOL_NAME` to separate the task from the main PHP pool. This is helpful when debugging or monitoring the task. -:: - +Here is a simplified example of how you can achieve this with Docker Compose: ::code-panel --- label: Example & Simplified Docker Compose File From 3435d394efc81e4e3b27f370e265b57e787bd852 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:21:06 -0500 Subject: [PATCH 31/48] Update Laravel queue and Laravel Horizon documentation --- docs/content/docs/4.laravel/3.laravel-queue.md | 10 +++++----- docs/content/docs/4.laravel/4.laravel-horizon.md | 7 +++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/content/docs/4.laravel/3.laravel-queue.md b/docs/content/docs/4.laravel/3.laravel-queue.md index 72ed2713..6adf47a8 100644 --- a/docs/content/docs/4.laravel/3.laravel-queue.md +++ b/docs/content/docs/4.laravel/3.laravel-queue.md @@ -9,7 +9,9 @@ All you need to do is pass the Laravel Queue command to the container and S6 wil ## Important concepts 1. It's usually best to run the queue as a separate container (but using the same image) -2. If you need to run the queue in the same container, you might want to look into extending the image with S6 Overlay and have S6 monitor the process for you +1. Notice we're using the same `my/laravel-app` image for both the PHP and Queue services. This is a common practice to keep the image consistent. +1. You can do cool things with `PHP_FPM_POOL_NAME` to separate the task from the main PHP pool. This is helpful when debugging or monitoring the task. +1. If you need to run the queue in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. ::code-panel --- @@ -22,7 +24,7 @@ php artisan queue:work --tries=3 ::code-panel --- -label: Example Docker Compose File +label: Example & Simplified Docker Compose File --- ```yaml version: '3' @@ -34,9 +36,7 @@ services: queue: image: my/laravel-app - # Switch to "webuser" before running `php artisan` - # Declare command in list manner for environment variable expansion - command: ["su", "webuser", "-c", "php artisan queue:work --tries=3"] + command: ["php", "/var/www/html/artisan", "queue:work", "--tries=3"] environment: PHP_FPM_POOL_NAME: "my-app_queue" ``` diff --git a/docs/content/docs/4.laravel/4.laravel-horizon.md b/docs/content/docs/4.laravel/4.laravel-horizon.md index fbdefdd0..81e0bd34 100644 --- a/docs/content/docs/4.laravel/4.laravel-horizon.md +++ b/docs/content/docs/4.laravel/4.laravel-horizon.md @@ -9,9 +9,10 @@ We simply pass the command to the Docker container and let S6 Overlay monitor th ## Important concepts 1. In most cases, you probably want to run this as a separate container from your web container -1. If you want to run this in the same container, then you should create a custom S6 script to bring the service up and to monitor it 1. Ensure that you have your `.env` configured correctly to authenticate with Redis 1. Ensure Redis is running before you attempt to connect Horizon to Redis +1. You can do cool things with `PHP_FPM_POOL_NAME` to separate the task from the main PHP pool. This is helpful when debugging or monitoring the task. +1. If you need to run horizon in the same container, you might want to look into [writing your own S6 Overlay script](/docs/guide/using-s6-overlay#customizing-the-initialization-process) to manage and monitor multiple processes in one container. ::code-panel --- @@ -40,9 +41,7 @@ services: horizon: image: my/laravel-app - # Switch to "webuser" before running `php artisan` - # Declare command in list manner for environment variable expansion - command: ["su", "webuser", "-c", "php artisan horizon"] + command: ["php", "/var/www/html/artisan", "horizon"] environment: PHP_FPM_POOL_NAME: "my-app_horizon" ``` From ecd6eaa14f3fc10eae46e44ed0d3cc9230fd2940 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:30:09 -0500 Subject: [PATCH 32/48] Clear config cache before running migrations. Fixes #303 --- src/common/etc/entrypoint.d/50-laravel-automations.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 1f32a0de..8c9b6e7b 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -47,6 +47,11 @@ if [ "$DISABLE_DEFAULT_CONFIG" = "false" ]; then if [ "${AUTORUN_LARAVEL_MIGRATION:=true}" = "true" ]; then count=0 timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT + + # Ensure the configuration cache is cleared before running migrations + echo "🚀 Clearing Laravel cache before attempting migrations..." + php "$APP_BASE_DIR/artisan" config:clear + while [ $count -lt "$timeout" ]; do test_db_connection > /dev/null 2>&1 status=$? From c4e3eaba53b38bf67dbb5274f6c6a244c2ce1c2b Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 10:45:33 -0500 Subject: [PATCH 33/48] Remove comment --- src/common/etc/entrypoint.d/50-laravel-automations.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 8c9b6e7b..e4358e6a 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -48,7 +48,6 @@ if [ "$DISABLE_DEFAULT_CONFIG" = "false" ]; then count=0 timeout=$AUTORUN_LARAVEL_MIGRATION_TIMEOUT - # Ensure the configuration cache is cleared before running migrations echo "🚀 Clearing Laravel cache before attempting migrations..." php "$APP_BASE_DIR/artisan" config:clear From d1aa4aec9d1da3528823c16a49f6e1726cb01ea6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:08:37 -0500 Subject: [PATCH 34/48] Added file permissions docs --- .../2.understanding-file-permissions.md | 116 ++++++++++++++++++ .../images/docs/permissions-privileged.png | Bin 0 -> 84041 bytes 2 files changed, 116 insertions(+) create mode 100644 docs/content/docs/3.guide/2.understanding-file-permissions.md create mode 100644 docs/public/images/docs/permissions-privileged.png diff --git a/docs/content/docs/3.guide/2.understanding-file-permissions.md b/docs/content/docs/3.guide/2.understanding-file-permissions.md new file mode 100644 index 00000000..37dc5c53 --- /dev/null +++ b/docs/content/docs/3.guide/2.understanding-file-permissions.md @@ -0,0 +1,116 @@ +--- +head.title: 'Understanding file permissions - Docker PHP - Server Side Up' +description: 'Learn how easy it is to move from the official PHP docker images to serversideup/php.' +layout: docs +--- + +# Understanding File Permissions +::lead-p +Working with file permissions is one of the biggest headaches when working with PHP + Docker. This generally is because the PHP server also requires a web server to serve static files. By default, this means multiple users are created in the container, and permissions can get out of hand quickly. +:: + +![Traditional PHP File Permissions Configuration](/images/docs/permissions-privileged.png) + +## Even more frustrating: Development Environments +Even if someone configured a single user in the container to run both the PHP server and the web server, things get even more complicated in development environments. For example, if you have Alice running her Windows Machine with WSL2, she might have a user ID of `1001`. Then you have Bob running his Ubuntu workstation with a user ID of `1002`. Meanwhile, Charlie is running his Docker on his macOS machine (that runs a tiny VM) that has a totally different file permission experience compared to Windows and Linux because of the file system differences. + +If a volume is mounted from the container to the host, the container will write files to the host as `33:33`, which will require sudo/root permissions to edit and delete files. + +## Our industry attempted workarounds +We've seen experiences that allow users to provide an environment variable of `PUID` and `PGID`. Although this is a great user experience, it requires the container user to be privileged, which is a major "no-no" in the security world. It also had downstream file permission errors if the container failed on initialization where logs would be created by the root user and no longer writable by the `www-data` user. + +## Our solution +We focus on providing the tools to give sysadmins the ability to: +- Keep their containers unprivileged by default +- Allow the dynamic reconfiguration of the container user and group ID (at build time only) + +It's a bummer that we can only set the user and group ID at build time, but it's a small price to pay for the security benefits of running unprivileged containers. + +#### How it works +- By default, all our images run `www-data` as the user (`33:33` for Debian and `82:82` for Alpine) +- We provide a script that can be called at build time to change the UID and GID of `www-data` (called `docker-php-serversideup-set-id`) +- If you need to update permissions of service files (example: NGINX, Apache, Unit, etc), you can run the `docker-php-serversideup-set-id` at build +- We will use a multi-stage build to ensure that the `docker-php-serversideup-set-id` script is not included in the final image + +## Example +Here's an example of ensuring our UID/GID of `www-data` will match the development UID/GID of the host machine, while preserving the default UID/GID of `33:33` for the final image: + +::code-panel +--- +label: Dockerfile +--- +```dockerfile +############################################ +# Base Image +############################################ +FROM serversideup/php:8.3-fpm-nginx-bookworm as base + +############################################ +# Development Image +############################################ +FROM base as development + +# Switch to root so we can do root things +USER root + +# Save the build arguments as a variable +ARG USER_ID +ARG GROUP_ID + +# Use the build arguments to change the UID +# and GID of www-data while also changing +# the file permissions for NGINX +RUN docker-php-serversideup-set-id www-data $USER_ID $GROUP_ID && \ + \ + # Update the file permissions for our NGINX service to match the new UID/GID + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + +# Drop back to our unprivileged user +USER www-data + +############################################ +# Production Image +############################################ + +# Since we're calling "base", production isn't +# calling any of that permission stuff +FROM base as production + +# Copy our app files as www-data (33:33) +COPY --chown=www-data:www-data . /var/www/html +``` +:: + +To show a simple Docker Compose file example for development, we could use: + +::code-panel +--- +label: docker-compose.yml +--- +```yaml +services: + php: + build: + context: . + target: development + args: + # UID and GID must be set as environment variables on the host machine + USER_ID: $UID + GROUP_ID: $GID + ports: + - 80:8080 + volumes: + - .:/var/www/html +``` +:: + +When we run `docker compose up`, our compose file directs us to build the `development` target. This target will run the `docker-php-serversideup-set-id` script to change the UID and GID of `www-data` to match the host machine (assuming `$UID` and `$GID` are set in a zsh/bash profile or something similar). This will allow us to run the container as an unprivileged user while still having the correct permissions to read and write files. + +The best thing is the user can delete files off of their machine without being prompted for sudo permissions. This is because we're aligning the UID/GID of the container with the host machine. + +When it comes to building our image for production, we just use the `production` target, which will copy the files as `www-data` with the default UID/GID of `33:33`. + +## An optimized experience from development to production +If you like the concepts above and you're looking for an optimized experience for developers (especially when it comes simplifying the setting of UID/GID), we recommend checking out our other open source project Spin. Spin is a lightweight wrapper for Docker Compose that allows you to manage your environment from development to production. + +[Learn more about Spin →](https://serversideup.net/open-source/spin/) \ No newline at end of file diff --git a/docs/public/images/docs/permissions-privileged.png b/docs/public/images/docs/permissions-privileged.png new file mode 100644 index 0000000000000000000000000000000000000000..2aeb3977ef45189ce17dcea56a1e82318a4d2e4b GIT binary patch literal 84041 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

9JlI=s?lQTc~6*0#kt|i$~ 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 U0f$p%|mAmH+?% literal 0 HcmV?d00001 From 489e775f6a0de86e4748eae95f3d186114f3f185 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:08:44 -0500 Subject: [PATCH 35/48] Update Docker image migration guide --- .../3.guide/100.migrating-from-v2-to-v3.md | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md index e89368e6..a1a6c6ec 100644 --- a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md +++ b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md @@ -52,12 +52,11 @@ services: All you need to do is add `-v2.2.1` to the end of the image tag. This will ensure that you're not automatically upgraded to the v3 images. -### Testing the new images -You can start preparing for the upgrade by setting [`beta`](https://hub.docker.com/r/serversideup/php/tags?page=1&name=beta) to the image tag. This will allow you to test the new images in a development environment and ensure everything works well for the upcoming release. ## 🚀 New Features We've been busy overhauling our PHP Docker Images to make them more production-ready and easier to use. Here are some of the new features we've added: - **Based on official PHP Images** - We're now building an improved developer experience on top of the official PHP Docker images. +- **Unprivileged by default** - We're now running our images as an unprivileged user by default. This is a huge step forward in security and compatibility. - **PHP 8.3 support** - We're now shipping the latest and greatest. - **Pin to the exact minor version** - Pin your app to the exact minor version of PHP that you want to use. This means you can pin to `8.2.12` instead of `8.2`. - **Easier start up script customization** - We now have a folder called `/etc/entrypoint.d` that allows you to easily customize your container with scripts. Just put them in numerical order and we'll execute any shell script you want. No S6 Overlay knowledge required. @@ -77,15 +76,12 @@ Since we're using PHP.net as the "official source of truth" for getting our PHP [Learn how to install your own PHP extension →](/docs/guide/installing-additional-php-extensions) ### `webuser` is no longer being used -::note -This is still an area that we're exploring. Please chime in on the discussion if you have any thoughts. - -[See the discussion →](https://github.com/serversideup/docker-php/issues/253) -:: +We used to add a user called `webuser` with the UID of `9999` with shell permissions. To increase security, we're now using the `www-data` user and group that is built into the official PHP images. If you have mounted volumes, you will need to `chown` the files to match the ID of the `www-data` user and groups. For Debian, this is `33:33` and for Alpine, this is `82:82`. -We used to add a user called `webuser` with the UID of `9999`, but in the current beta we're not. This is an area that we're still going to explore during the beta, so please chime in on the discussion if you have any thoughts. +### NGINX and Apache listen on 8080 (HTTP) and 8443 (HTTPS) by default +Our images are now unprivileged by default. This is a major step forward in security and compatibility. Since we are unprivileged by default, we lose the ability to mount on ports less than 1024. If you're using NGINX or Apache, you will need to update your port mappings to use `8080` and `8443` instead of `80` and `443`. -If you have mounted volumes, you will need to `chown` the files to match the ID of the `www-data` user and groups. For Debian, this is `33:33` and for Alpine, this is `82:82`. +[Learn more about this change →](/docs/getting-started/default-configurations#unprivileged-by-default) ### S6 Overlay is only used in `*-fpm-apache` and `*-fpm-nginx` images Due to compatibility issues, we only use S6 Overlay in our `*-fpm-apache` and `*-fpm-nginx` images. If you were using S6 Overlay for our other variations (cli, fpm, etc), you will need to migrate your scripts to use the new `/etc/entrypoint.d` folder. @@ -96,6 +92,34 @@ Running end-to-end SSL by default created more problems than good. By default, w ### `AUTORUN_ENABLED` is now set to `false` by default. Having this set to "true" by default also created more problems than good. If you want to use any of the Laravel Automation Scripts, be sure to set this to `true`. +### MSMTP is no longer included in the images +For security and image size reasons, we removed MSMTP from the images. If you need to send emails, use an external SMTP service like Postmark/Sendgrid/Mailgun. You can also extend the image yourself to include MSMTP specifically for your use case. + ### Variable deprecations - `WEB_APP_DIRECTORY` has now been renamed to `APP_BASE_DIR` -- `DEBUG_OUTPUT` has been removed for in favor of `LOG_OUTPUT_LEVEL=debug` \ No newline at end of file +- `DEBUG_OUTPUT` has been removed for in favor of `LOG_OUTPUT_LEVEL=debug` +- `PUID` & `PGID` are no longer used because it requires root privileges. See the [new way to set the UID and GID →](/docs/guide/understanding-file-permissions) +- `MSMTP_RELAY_SERVER_HOSTNAME` & `MSMTP_RELAY_SERVER_PORT` are no longer used because MSMTP is no longer included in the images. + +## 📝 Migration Checklist +Here is a good list to perform the migration + +#### Repository +- Ensure you're committing to a test environment + +#### Docker Compose +- Check each environment variable exists and is set to a proper value [See the full list of environment variables →](/docs/reference/environment-variable-specification) +- Update the image name (if applicable) + +#### Dockerfile +- Update the base image name (if applicable) +- Remove any `ppa:ondrej/php` references +- Remove any Ubuntu specific commands +- Ensure all extensions are installed with the `install-php-extensions` command [Learn how to install your own PHP extension →](/docs/customizing-the-image/installing-additional-php-extensions) + +#### Production/Staging Servers +- Update all host volume file permissions to match the `www-data` UID/GID (`33:33` for Debian, `82:82` for Alpine) [Learn how to manage file permissions](/docs/guide/understanding-file-permissions) + +#### Deployment +- CI/CD with valid tests is always encouraged +- After completing all steps above, you're now ready to deploy the new images \ No newline at end of file From 5778e386b9542d6621e04b0079e484decd450f37 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:09:06 -0500 Subject: [PATCH 36/48] Removed old beta docs --- .../1.environment-variable-specification.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/content/docs/7.reference/1.environment-variable-specification.md b/docs/content/docs/7.reference/1.environment-variable-specification.md index 2363c9fa..15007c23 100644 --- a/docs/content/docs/7.reference/1.environment-variable-specification.md +++ b/docs/content/docs/7.reference/1.environment-variable-specification.md @@ -68,15 +68,4 @@ We like to customize our images on a per app basis using environment variables. `UNIT_CERTIFICATE_NAME`
*Default: "self-signed-web-bundle"*| Name of your certificate bundle. This is used to configure HTTPS. (
Official Docs)| unit `UNIT_CONFIG_DIRECTORY`
*Default: "/etc/unit/config.d"*|Path to the Unit configuration directory. Any *.json, *.js, and *.pem files will be loaded into Unit on initialization.| unit `UNIT_CONFIG_FILE`
*Default: "/etc/unit/config.d/config.json"*|Path to the Unit configuration file. One will be generated automatically by default. (Official Docs)| unit -`UNIT_WEBROOT`
*Default: "`$APP_BASE_DIR`/public"*|Base directory of the app’s file structure. All URI paths are relative to it. (Official Docs)| unit - -# Variables that have yet to be added in the beta -::note -This section is reserved for the maintainers as we're working on migrating old docs over to the new image format. Any variable listed below **will not** work in the current `beta` release. -:: -**Variable Name**|**Description**|**Used in variation** -:-----:|:-----:|:-----: -`PUID`
*Default: 9999*|User ID the webserver and PHP should run as.|all -`PGID`
*Default: 9999*|Group ID the webserver and PHP should run as.|all -`MSMTP_RELAY_SERVER_HOSTNAME`
*Default: "mailhog"

🚨 IMPORTANT: Change this value if you want emails to work. (we set it to Mailhog so our staging sites do not send emails out)*|Server that should relay emails for MSMTP. (Official docs)|fpm-nginx,
fpm-apache -`MSMTP_RELAY_SERVER_PORT`
*Default: "1025" (default port for Mailhog)*|Port the SMTP server is listening on. (Official docs)|fpm-nginx,
fpm-apache \ No newline at end of file +`UNIT_WEBROOT`
*Default: "`$APP_BASE_DIR`/public"*|Base directory of the app’s file structure. All URI paths are relative to it. (Official Docs)| unit \ No newline at end of file From cf2766761fc941d76606a71c02db36f4a1db5ab8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:14:04 -0500 Subject: [PATCH 37/48] Added privileges --- .../2.installing-additional-php-extensions.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md b/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md index a9a49e58..50a25ea3 100644 --- a/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md +++ b/docs/content/docs/5.customizing-the-image/2.installing-additional-php-extensions.md @@ -9,6 +9,10 @@ We provide a number of PHP packages by default, but there will be times where yo Installing extensions is easy. All you need to do is find the extension you'd like to install on the [docker-php-extension installer README](https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions). Once you have the name of the extensions, you'll need to add it to the `Dockerfile` in your project. +::note +**Our images are unprivileged by default.** This means you'll need to switch to `root` to do "root things", then switch back to the `www-data` user. This ensures your container image is hardened against security vulnerabilities. +:: + ::code-panel --- label: "Example: Installing the \"intl\" extension" @@ -16,7 +20,14 @@ label: "Example: Installing the \"intl\" extension" ```dockerfile FROM serversideup/php:8.2.12-fpm-nginx-bookworm +# Switch to root so we can do root things +USER root + +# Install the intl extension with root permissions RUN install-php-extensions intl + +# Drop back to our unprivileged user +USER www-data ``` :: @@ -64,7 +75,14 @@ label: "New File: Dockerfile" ```dockerfile FROM serversideup/php:8.2.12-fpm-nginx-bookworm +# Switch to root so we can do root things +USER root + +# Install the imagick extension with root permissions RUN install-php-extensions imagick + +# Drop back to our unprivileged user +USER www-data ``` :: From 6fa120e5115e39cc63ead74f4ea1431adaea1eb8 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:43:15 -0500 Subject: [PATCH 38/48] Update command reference documentation --- .../docs/7.reference/2.command-reference.md | 107 +++++++++++++++++- 1 file changed, 102 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/7.reference/2.command-reference.md b/docs/content/docs/7.reference/2.command-reference.md index 10acf015..af9d556c 100644 --- a/docs/content/docs/7.reference/2.command-reference.md +++ b/docs/content/docs/7.reference/2.command-reference.md @@ -4,11 +4,108 @@ description: 'Learn about all the commands available within the serversideup/php layout: docs --- -### serversideup/php commands +# Command Reference +::lead-p +We included a few commands in the `/usr/local/bin` directory to help you with your development experience. These commands are available in the `serversideup/php` image. +:: + ::note -**This page is a work in progress.** We will be adding more commands and documentation soon. +Since our images are unprivileged by default, you'll need to switch to the `root` user to run these commands. This is a security feature that ensures that your container is as secure as possible. +:: + +## Official PHP Docker Commands +Since our images are based off the official PHP Docker images, any commands from the Official PHP Docker images are available in our images. You can find the full list of commands on the [official PHP Docker images documentation](https://hub.docker.com/_/php). + +## docker-php-serversideup-dep-install-alpine +This command will detect the operating system and install packages if it is an Alpine based system. This is helpful if you're building multiple operating systems from the same Dockerfile. + +::code-panel +--- +label: Example - Installing the `git` package on Alpine +--- +```bash +# Usage: docker-php-serversideup-dep-install-alpine [alpine-packages] +docker-php-serversideup-dep-install-alpine git +``` +:: + +## docker-php-serversideup-dep-install-debian +This command will detect the operating system and install packages if it is a Debian based system. This is helpful if you're building multiple operating systems from the same Dockerfile. + +::code-panel +--- +label: Example - Installing the `git` package on Debian +--- +```bash +# Usage: docker-php-serversideup-dep-install-debian [debian-packages] +docker-php-serversideup-dep-install-debian git +``` +:: + +## docker-php-serversideup-entrypoint +For our images that **DO NOT use S6 Overlay**, this is our default entrypoint script. + +## docker-php-serversideup-install-php-ext-installer +This is an internal helper script to shorten up the syntax for the installation of the PHP extension installer. + +::code-panel +--- +label: Example - Installing the PHP extension installer +--- +```bash +# Usage: docker-php-serversideup-install-php-ext-installer [version] +docker-php-serversideup-install-php-ext-installer 2.2.0 +``` :: -We have a few commands available to help you with your development experience. These commands are available in the `serversideup/php` image. -### Official PHP Docker Commands -We will document the official PHP Docker commands here for reference. These commands are available in the `serversideup/php` image. \ No newline at end of file +## docker-php-serversideup-set-file-permissions +This command is used to set the file permissions of a service in the container. This is useful for development environments where you want to match the user and group ID of the host machine. + +[Learn more about working with file permissions →](/docs/guide/understanding-file-permissions) + +::code-panel +--- +label: Example - Setting the file permissions of the NGINX service +--- +```bash +# Usage: docker-php-serversideup-set-file-permissions --owner USER:GROUP --service SERVICE +docker-php-serversideup-set-file-permissions --owner 1000:1000 --service nginx +``` +:: + +## docker-php-serversideup-set-id +This command is used to set the user and group ID of the `www-data` user in the container. This is useful for development environments where you want to match the user and group ID of the host machine. + +[Learn more about working with file permissions →](/docs/guide/understanding-file-permissions) + +::code-panel +--- +label: Example - Setting the user and group ID of the `www-data` user +--- +```bash +# docker-php-serversideup-set-id [username] [uid] [gid] +docker-php-serversideup-set-id www-data 1000 1000 +``` +:: + +## docker-php-serversideup-s6-init +This is a command used at build time to take scripts from "/etc/entrypoint.d" and move them to the S6 Overlay structure. + +## docker-php-serversideup-s6-install +This is a command used at build time to install a specific version of S6 Overlay. + +## install-php-extensions +This command is a wrapper around the `docker-php-ext-install` command that comes with the official PHP Docker images. This command allows you to install PHP extensions with a single command. + +[Check out the docker-php-extension-installer project on GitHub →](https://github.com/mlocati/docker-php-extension-installer) + +Here's an example of how you can use this command: + +::code-panel +--- +label: Example - Installing the "intl" extension +--- +```bash +install-php-extensions intl +``` +:: \ No newline at end of file From 65fcce580eaaaf4e7b100f709c1c47ff1b9263c5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:45:23 -0500 Subject: [PATCH 39/48] Update contributing guide with link to docs contribution instructions --- docs/content/docs/2.getting-started/7.contributing.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/content/docs/2.getting-started/7.contributing.md b/docs/content/docs/2.getting-started/7.contributing.md index 3801dbdd..23fb1756 100644 --- a/docs/content/docs/2.getting-started/7.contributing.md +++ b/docs/content/docs/2.getting-started/7.contributing.md @@ -7,6 +7,11 @@ layout: docs # Contributing Thanks for your interest in contributing to this project! Please use read this entire guide before submitting a pull request. +## Improve the docs +All our docs are located under `/docs`. Contributing is very easy and is documented under `/docs/README.md`. + +[Learn how to contribute to the docs](https://github.com/serversideup/docker-php/tree/main/docs) + ## Project dependencies You must have these installed on your system. * Docker (container system): https://www.docker.com/products/docker-desktop From a9ef65e92e16980e2b6b3ee2d7447f64e2bec1b6 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:52:27 -0500 Subject: [PATCH 40/48] Fixed links --- docs/content/docs/2.getting-started/2.installation.md | 2 +- docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md | 2 +- docs/content/docs/3.guide/2.using-s6-overlay.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/2.getting-started/2.installation.md b/docs/content/docs/2.getting-started/2.installation.md index 3df12a8b..1e5805ce 100644 --- a/docs/content/docs/2.getting-started/2.installation.md +++ b/docs/content/docs/2.getting-started/2.installation.md @@ -39,7 +39,7 @@ The `fpm` variation is great for people who need to run a PHP "backend" if they ### FPM-Apache The `fpm-apache` variation is meant for users who want to run something like WordPress with Docker. Apache is configured to be a "reverse proxy", which will serve any static content with Apache and serve any PHP requests with PHP-FPM. Since there are two processes required to run this variation, we use [S6 Overlay](/docs/guide/using-s6-overlay) to ensure the container health is accurate. -[Learn more about using Docker with WordPress →](/docs/wordpress/wordpress-optimizations) +[Learn more about using Docker with WordPress →](/docs/guide/using-wordpress-with-docker) ### FPM-NGINX The `fpm-nginx` variation is great for people who want to run Laravel applications or similar. This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two proccess required to run this variation. We use S6 Overlay to ensure the container health is accurate. diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md index a1a6c6ec..e508885a 100644 --- a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md +++ b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md @@ -73,7 +73,7 @@ We now use Debian or Alpine as our base OS (because we're using the official PHP ### `ppa:ondrej/php` is no longer used Since we're using PHP.net as the "official source of truth" for getting our PHP versions, this means we're also dropping support for the `ppa:ondrej/php` repostory. If you're using things like `apt-get install php-redis` you will need to change your method of installing PHP extensions. -[Learn how to install your own PHP extension →](/docs/guide/installing-additional-php-extensions) +[Learn how to install your own PHP extension →](/docs/customizing-the-image/installing-additional-php-extensions) ### `webuser` is no longer being used We used to add a user called `webuser` with the UID of `9999` with shell permissions. To increase security, we're now using the `www-data` user and group that is built into the official PHP images. If you have mounted volumes, you will need to `chown` the files to match the ID of the `www-data` user and groups. For Debian, this is `33:33` and for Alpine, this is `82:82`. diff --git a/docs/content/docs/3.guide/2.using-s6-overlay.md b/docs/content/docs/3.guide/2.using-s6-overlay.md index eab39f1e..2c2e7f19 100644 --- a/docs/content/docs/3.guide/2.using-s6-overlay.md +++ b/docs/content/docs/3.guide/2.using-s6-overlay.md @@ -93,4 +93,4 @@ Both scripts must finish successfully before S6 Overlay starts our main `php-fpm As you can see this structure can be very powerful in making your own customizations. This is great for giving you full control of how you'd like your application to behave. -[Read how to make customizations →](/docs/guide/changing-common-php-settings/) \ No newline at end of file +[Read how to make customizations →](/docs/customizing-the-image/changing-common-php-settings) \ No newline at end of file From a06c0edb0c45ffb7caf8a8856fc8bcc0d1d5e112 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 12:53:58 -0500 Subject: [PATCH 41/48] Update static site generation command in service_deploy-static-site.yml --- .github/workflows/service_deploy-static-site.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/service_deploy-static-site.yml b/.github/workflows/service_deploy-static-site.yml index ec62b629..fe0cb95c 100644 --- a/.github/workflows/service_deploy-static-site.yml +++ b/.github/workflows/service_deploy-static-site.yml @@ -33,8 +33,7 @@ jobs: - run: | yarn install --frozen-lockfile - yarn build - npx nuxi generate + yarn generate working-directory: ./docs - name: Publish to Cloudflare Pages From 8b63f6b53a2edc31838db200d434741c9af8ffa3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 17:01:31 -0500 Subject: [PATCH 42/48] Added helpful notes to the migration guide --- .../3.guide/100.migrating-from-v2-to-v3.md | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md index e508885a..37f85acd 100644 --- a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md +++ b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md @@ -108,17 +108,53 @@ Here is a good list to perform the migration - Ensure you're committing to a test environment #### Docker Compose -- Check each environment variable exists and is set to a proper value [See the full list of environment variables →](/docs/reference/environment-variable-specification) - Update the image name (if applicable) +- Check each environment variable exists and is set to a proper value [See the full list of environment variables →](/docs/reference/environment-variable-specification) +- Ensure you updated the ports to `8080` and `8443` for NGINX, Apache, and Unit #### Dockerfile - Update the base image name (if applicable) - Remove any `ppa:ondrej/php` references - Remove any Ubuntu specific commands - Ensure all extensions are installed with the `install-php-extensions` command [Learn how to install your own PHP extension →](/docs/customizing-the-image/installing-additional-php-extensions) +- Ensure your `COPY` commands are copying with the correct permissions (i.e. `--chown=www-data:www-data`) + +#### CI/CD +If you're running `fpm-nginx` (or similar) on a runner that's running as your builds as `root`, you may need to add `user = www-data` and `group = www-data` to your `php-fpm.conf` file so you can bring FPM up correctly. + +If you have to run things as root in CI, you can do this with a multi stage build and set the targets: + +::code-panel +--- +label: Dockerfile +--- +```dockerfile +FROM serversideup/php:8.3-fpm-nginx as base + +#################################################### +# CI +#################################################### +FROM base as ci + +# Switch to the root user +USER root + +# Set our user and group to www-data for +# FPM so it doesn't fail on startup +RUN echo "user = www-data" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf && \ + echo "group = www-data" >> /usr/local/etc/php-fpm.d/docker-php-serversideup-pool.conf + +#################################################### +# Production +#################################################### +FROM base as deploy +COPY --chown=www-data:www-data . /var/www/html +``` +:: #### Production/Staging Servers - Update all host volume file permissions to match the `www-data` UID/GID (`33:33` for Debian, `82:82` for Alpine) [Learn how to manage file permissions](/docs/guide/understanding-file-permissions) +- If you're running Docker Swarm with host volume mounts, we created a script that could potentially help ([change-volume-permissions.sh](https://github.com/serversideup/docker-volume-change-permission-script)) #### Deployment - CI/CD with valid tests is always encouraged From 9b558e445abc7290f1e8ed7e96ecec7bdeb0bc09 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 17:17:46 -0500 Subject: [PATCH 43/48] Changed format of docker-php-serversideup-set-id --- .../2.understanding-file-permissions.md | 2 +- .../docs/7.reference/2.command-reference.md | 4 ++-- .../local/bin/docker-php-serversideup-set-id | 21 +++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/content/docs/3.guide/2.understanding-file-permissions.md b/docs/content/docs/3.guide/2.understanding-file-permissions.md index 37dc5c53..6b8381a6 100644 --- a/docs/content/docs/3.guide/2.understanding-file-permissions.md +++ b/docs/content/docs/3.guide/2.understanding-file-permissions.md @@ -60,7 +60,7 @@ ARG GROUP_ID # Use the build arguments to change the UID # and GID of www-data while also changing # the file permissions for NGINX -RUN docker-php-serversideup-set-id www-data $USER_ID $GROUP_ID && \ +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ \ # Update the file permissions for our NGINX service to match the new UID/GID docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx diff --git a/docs/content/docs/7.reference/2.command-reference.md b/docs/content/docs/7.reference/2.command-reference.md index af9d556c..3430cdd7 100644 --- a/docs/content/docs/7.reference/2.command-reference.md +++ b/docs/content/docs/7.reference/2.command-reference.md @@ -83,8 +83,8 @@ This command is used to set the user and group ID of the `www-data` user in the label: Example - Setting the user and group ID of the `www-data` user --- ```bash -# docker-php-serversideup-set-id [username] [uid] [gid] -docker-php-serversideup-set-id www-data 1000 1000 +# docker-php-serversideup-set-id [username] [uid]:[gid] +docker-php-serversideup-set-id www-data 1000:1000 ``` :: diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-id b/src/common/usr/local/bin/docker-php-serversideup-set-id index 6dea3890..908507d7 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-id +++ b/src/common/usr/local/bin/docker-php-serversideup-set-id @@ -2,24 +2,33 @@ set -e ################################################### -# Usage: docker-php-serversideup-set-id [username] [uid] [gid] +# Usage: docker-php-serversideup-set-id [username] [uid]:[gid] ################################################### # This script is intended to be called on build for sysadmins who want to -# change the UID and GID of a specific user. This is useful for when you +# change the UID and GID of a specific user in a format [uid]:[gid]. This is useful for when you # want to match the UID and GID of the host machine to the container. # Specifically, this can be helpful to call during a build target in development # so developers don't need to worry about permissions issues. script_name="docker-php-serversideup-set-id" # Sanity checks -if [ "$#" -ne 3 ]; then - echo "Usage: $script_name [username] [uid] [gid]" +if [ "$#" -ne 2 ]; then + echo "Usage: $script_name [username] [uid]:[gid]" exit 1 fi username="$1" -uid="$2" -gid="$3" +uid_gid="$2" + +# Split UID and GID +uid=$(echo "$uid_gid" | cut -d':' -f1) +gid=$(echo "$uid_gid" | cut -d':' -f2) + +# Verify that uid and gid are numeric +if ! echo "$uid" | grep -qE '^[0-9]+$' || ! echo "$gid" | grep -qE '^[0-9]+$'; then + echo "$script_name: UID and GID must be numeric." + exit 1 +fi # Check if the user exists if ! id "$username" > /dev/null 2>&1; then From 786b75c734ad213056322f64ed46a9b3da3e63da Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 17:33:24 -0500 Subject: [PATCH 44/48] Added /var/www/html/ to chown --- .../bin/docker-php-serversideup-set-file-permissions | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions index e048fe50..7b42c8df 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions +++ b/src/common/usr/local/bin/docker-php-serversideup-set-file-permissions @@ -70,13 +70,13 @@ case "$OS" in debian) case "$SERVICE" in apache) - DIRS="/etc/apache2 /etc/ssl/private /var/log/apache2" + DIRS="/etc/apache2 /etc/ssl/private /var/log/apache2 /var/www/html/" ;; nginx) - DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/" + DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/cache/nginx/ /var/www/html/" ;; unit) - DIRS="/var/log/unit /var/run/unit /etc/unit /etc/ssl/private/ /var/lib/unit/" + DIRS="/var/log/unit /var/run/unit /etc/unit /etc/ssl/private/ /var/lib/unit/ /var/www/html/" ;; *) echo "$script_name: Unsupported service: $SERVICE" @@ -87,10 +87,10 @@ case "$OS" in alpine) case "$SERVICE" in apache) - DIRS="/etc/apache2 /etc/ssl/private" + DIRS="/etc/apache2 /etc/ssl/private /var/www/html/" ;; nginx) - DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/lib/nginx" + DIRS="/etc/nginx/ /var/log/nginx /etc/ssl/private /var/lib/nginx /var/www/html/" ;; *) echo "$script_name: Unsupported SERVICE: $SERVICE" From 8c45015565446334034284881806afaf0d2ba8f3 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 17:46:09 -0500 Subject: [PATCH 45/48] Update migration guide with PHP_FPM_POOL_NAME rename --- docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md index 37f85acd..a8aa4c65 100644 --- a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md +++ b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md @@ -100,6 +100,7 @@ For security and image size reasons, we removed MSMTP from the images. If you ne - `DEBUG_OUTPUT` has been removed for in favor of `LOG_OUTPUT_LEVEL=debug` - `PUID` & `PGID` are no longer used because it requires root privileges. See the [new way to set the UID and GID →](/docs/guide/understanding-file-permissions) - `MSMTP_RELAY_SERVER_HOSTNAME` & `MSMTP_RELAY_SERVER_PORT` are no longer used because MSMTP is no longer included in the images. +- `PHP_POOL_NAME` has been renamed to `PHP_FPM_POOL_NAME` ## 📝 Migration Checklist Here is a good list to perform the migration From 80c5fa4079660fec865252b9f52d42201f32c0bb Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 19:10:37 -0500 Subject: [PATCH 46/48] Fix SSL keypair generation in 10-init-webserver-config.sh --- src/s6/etc/entrypoint.d/10-init-webserver-config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh index 45ec01dc..e6c603f5 100644 --- a/src/s6/etc/entrypoint.d/10-init-webserver-config.sh +++ b/src/s6/etc/entrypoint.d/10-init-webserver-config.sh @@ -139,7 +139,7 @@ validate_ssl(){ fi echo "🔐 SSL Keypair not found. Generating self-signed SSL keypair..." - openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.gitpod.io,*.ngrok.io,*.nip.io" -addext "basicConstraints=CA:FALSE" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 + openssl req -x509 -subj "/C=US/ST=Wisconsin/L=Milwaukee/O=IT/CN=*.dev.test,*.gitpod.io,*.ngrok.io,*.nip.io" -nodes -newkey rsa:2048 -keyout "$SSL_PRIVATE_KEY_FILE" -out "$SSL_CERTIFICATE_FILE" -days 365 >/dev/null 2>&1 } ########## From b44450fcb846798f13f74ad3b8c1ffe8955e53fa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 19:35:44 -0500 Subject: [PATCH 47/48] Add PHP_OPCACHE_ENABLE=1 to production environment for increased performance --- docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md index a8aa4c65..e875e814 100644 --- a/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md +++ b/docs/content/docs/3.guide/100.migrating-from-v2-to-v3.md @@ -112,6 +112,7 @@ Here is a good list to perform the migration - Update the image name (if applicable) - Check each environment variable exists and is set to a proper value [See the full list of environment variables →](/docs/reference/environment-variable-specification) - Ensure you updated the ports to `8080` and `8443` for NGINX, Apache, and Unit +- Consider adding `PHP_OPCACHE_ENABLE=1` to your production environment for increased performance #### Dockerfile - Update the base image name (if applicable) From b6fe999be81ded8209ddb17c40521844ee1830e7 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 18 Apr 2024 19:56:21 -0500 Subject: [PATCH 48/48] Improved get-php-versions script to accept null values and rely more on the Official PHP data --- scripts/conf/php-versions-base-config.yml | 8 ++++---- scripts/get-php-versions.sh | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index ab5bdbdf..92f18cc1 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -23,21 +23,21 @@ php_versions: - name: bookworm default: true patch_versions: - - 8.1.28 + # - 8.1.28 # Pull latest from Official PHP source - minor: "8.2" base_os: - name: alpine - name: bookworm default: true patch_versions: - - 8.2.18 + # - 8.2.18 # Pull latest from Official PHP source - minor: "8.3" base_os: - name: alpine - name: bookworm default: true patch_versions: - - 8.3.6 + # - 8.3.6 # Pull latest from Official PHP source php_variations: - name: cli default: true @@ -48,6 +48,6 @@ php_variations: - bookworm - name: fpm-nginx - name: unit - supported_os: # PHP doesn't include "embed SAPI" on Alpine (https://github.com/docker-library/php/pull/1355#issuecomment-1352087633) + supported_os: # Alpine with Unit is not supported yet. Submit a PR if you can help (https://github.com/serversideup/docker-php/issues/233) - bullseye - bookworm \ No newline at end of file diff --git a/scripts/get-php-versions.sh b/scripts/get-php-versions.sh index 9c1589ea..4a8e91e4 100755 --- a/scripts/get-php-versions.sh +++ b/scripts/get-php-versions.sh @@ -89,7 +89,7 @@ if [ "$SKIP_DOWNLOAD" = false ]; then to_entries[] | { \"minor\": .key, - \"patch_versions\": [ .value.version | tostring ] + \"patch_versions\": (if .value.version | type == \"null\" then [] elif .value.version | type == \"array\" then .value.version else [.value.version] end) } ] } @@ -114,17 +114,17 @@ if [ "$SKIP_DOWNLOAD" = false ]; then | map({ major: .[0].major, minor_versions: ( - map(.minor_versions[]) + map(.minor_versions[] | select(. != null)) | group_by(.minor) | map({ minor: .[0].minor, - base_os: (map(.base_os? // []) | add), - patch_versions: (map(.patch_versions[]) | flatten | unique) + base_os: (map(.base_os // []) | add), + patch_versions: (map(.patch_versions // []) | flatten | unique | select(. != null)) }) ) }) ), - php_variations: .[1].php_variations + php_variations: (. | map(.php_variations // []) | add) } ' <(echo "$downloaded_and_normalized_json_data") <(echo "$base_json_data"))