There are many reasons to use Docker for development, but if you don't have a PHP image which is tailored to dev and production, your daily work can become a big hassle.
In this session we'll go through what it takes to create a flexible Docker image providing PHP which fits both, development and production environments. We'll look at various aspects, like the operating system, PHP extensions, configuration, debugging, speed, security and the image size.
11. Base Image
- standards and tooling for other images
- common tools for application usage
! create your own base image
! reduce to a minimum, but not less
12. Which OS?
Alpine Linux
- smaller filesystem footprint
- more secure?
- only relevant in container context
- uses musl instead of glibc "
13. Which OS?
Debian
- larger filesystem footprint
- well-established security team
- wide-spread, therefore more first-hand
experience
- high compatibility due to glibc
14. Which OS?
! You need to feel comfortable working
with the OS and it must support all the
software you want to install
29. !"/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# Load lib
. "${FLOWNATIVE_LIB_PATH}/syslog-ng.sh"
. "${FLOWNATIVE_LIB_PATH}/supervisor.sh"
. "${FLOWNATIVE_LIB_PATH}/banner.sh"
banner_flownative "Demo Base Image"
eval "$(syslog_env)"
syslog_initialize
syslog_start
eval "$(supervisor_env)"
supervisor_initialize
supervisor_start
trap 'supervisor_stop; syslog_stop' SIGINT SIGTERM
if [[ "$*" = *"run"* ]]; then
supervisor_pid=$(supervisor_get_pid)
info "Entrypoint: Start up complete"
# We can't use "wait" because supervisord is not a direct child of this shell:
while [ -e "/proc/${supervisor_pid}" ]; do sleep 1.1; done
info "Good bye #"
else
"$@"
fi
33. !"/bin/bash
[…]
# ---------------------------------------------------------------------------------------
# build_create_directories() - Create directories and set access rights accordingly
#
# @global PHP_BASE_PATH
# @global BEACH_APPLICATION_PATH
# @return void
#
build_create_directories() {
mkdir -p
"${PHP_BASE_PATH}/bin"
"${PHP_BASE_PATH}/etc/conf.d"
"${PHP_BASE_PATH}/ext"
"${PHP_BASE_PATH}/tmp"
"${PHP_BASE_PATH}/log"
}
# ---------------------------------------------------------------------------------------
# build_adjust_permissions() - Adjust permissions for a few paths and files
#
# @global PHP_BASE_PATH
# @return void
#
build_adjust_permissions() {
chown -R root:root "${PHP_BASE_PATH}"
chmod -R g+rwX "${PHP_BASE_PATH}"
chown -R 1000
"${PHP_BASE_PATH}/etc"
"${PHP_BASE_PATH}/tmp"
}
# ---------------------------------------------------------------------------------------
# Main routine
case $1 in
init)
banner_flownative 'PHP'
build_create_directories
"&
clean)
build_adjust_permissions
"&
esac
34. !"/bin/bash
set -o errexit
set -o nounset
set -o pipefail
# Load lib
. "${FLOWNATIVE_LIB_PATH}/syslog-ng.sh"
. "${FLOWNATIVE_LIB_PATH}/supervisor.sh"
. "${FLOWNATIVE_LIB_PATH}/banner.sh"
banner_flownative PHP
eval "$(syslog_env)"
syslog_initialize
syslog_start
eval "$(supervisor_env)"
supervisor_initialize
supervisor_start
trap 'supervisor_stop; syslog_stop' SIGINT SIGTERM
if [[ "$*" = *"run"* ]]; then
supervisor_pid=$(supervisor_get_pid)
info "Entrypoint: Start up complete"
# We can't use "wait" because supervisord is not a direct child of this shell:
while [ -e "/proc/${supervisor_pid}" ]; do sleep 1.1; done
info "Good bye #"
else
"$@"
fi
35.
36. # ---------------------------------------------------------------------------------------
# Main routine
case $1 in
init)
banner_flownative 'PHP'
if [[ ! "${PHP_VERSION}" =~ ^8.[0-2] ]]; then
error "$ Unsupported PHP version '${PHP_VERSION}'"
exit 1
fi
build_create_directories
"&
prepare)
packages_install $(build_get_runtime_packages) 1>$(debug_device)
packages_install $(build_get_build_packages) 1>$(debug_device)
"&
build)
build_compile_php
"&
clean)
build_adjust_permissions
packages_remove $(build_get_build_packages) 1>$(debug_device)
packages_remove $(build_get_unnecessary_packages) 1>$(debug_device)
packages_remove_docs_and_caches 1>$(debug_device)
build_clean
"&
esac
37. # ------------------------------------------------------------------------------------------
# build_get_build_packages() - Returns a list of packages which are only needed for building
#
# @global PHP_BASE_PATH
# @return List of packages
#
build_get_build_packages() {
local packages="
autoconf
bison
build-essential
cmake
curl
file
pkg-config
re2c
unzip
libcurl4-openssl-dev
libfreetype6-dev
libgmp-dev
libicu-dev
libjpeg62-turbo-dev
libltdl-dev
libmariadb-dev
libmcrypt-dev
libonig-dev
libpng-dev
libpspell-dev
libpq-dev
libreadline6-dev
libsqlite3-dev
libssl-dev
libwebp-dev
libxml2-dev
libzip-dev
libbz2-dev
"
echo $packages
}
40. # ---------------------------------------------------------------------------------------
# Main routine
case $1 in
init)
banner_flownative 'PHP'
if [[ ! "${PHP_VERSION}" =~ ^8.[0-2] ]]; then
error "$ Unsupported PHP version '${PHP_VERSION}'"
exit 1
fi
build_create_directories
"&
prepare)
packages_install $(build_get_runtime_packages) 1>$(debug_device)
packages_install $(build_get_build_packages) 1>$(debug_device)
"&
build)
build_compile_php
"&
clean)
build_adjust_permissions
packages_remove $(build_get_build_packages) 1>$(debug_device)
packages_remove $(build_get_unnecessary_packages) 1>$(debug_device)
packages_remove_docs_and_caches 1>$(debug_device)
build_clean
"&
esac
41. # ---------------------------------------------------------------------------------------
# Main routine
case $1 in
init)
banner_flownative 'PHP'
if [[ ! "${PHP_VERSION}" =~ ^8.[0-2] ]]; then
error "$ Unsupported PHP version '${PHP_VERSION}'"
exit 1
fi
build_create_directories
"&
prepare)
packages_install $(build_get_runtime_packages) 1>$(debug_device)
packages_install $(build_get_build_packages) 1>$(debug_device)
"&
build)
build_compile_php
"&
clean)
build_adjust_permissions
packages_remove $(build_get_build_packages) 1>$(debug_device)
packages_remove $(build_get_unnecessary_packages) 1>$(debug_device)
packages_remove_docs_and_caches 1>$(debug_device)
build_clean
"&
esac
42. # ---------------------------------------------------------------------------------------
# build_compile_php() -
#
# @global PHP_BASE_PATH
# @return void
#
build_compile_php() {
local php_source_url
php_source_url="https:"'www.php.net/distributions/php-${PHP_VERSION}.tar.gz"
info "$ Downloading source code for PHP ${PHP_VERSION} from ${php_source_url} ""("
with_backoff "curl -sSL ${php_source_url} -o php.tar.gz" "15" ") (
error "Failed downloading PHP source from ${php_source_url}"
exit 1
)
mkdir -p "${PHP_BASE_PATH}/src"
tar -xf php.tar.gz -C "${PHP_BASE_PATH}/src" "$strip-components=1
rm php.tar.gz*
cd "${PHP_BASE_PATH}/src"
info "$ Generating build configuration ""("
./buildconf "$force >$(debug_device)
if [[ ! -f configure ]]; then
error "$ Failed generating build configuration, 'configure' not found"
# shellcheck disable=SC2012
ls | output
exit 1
fi
…
43. # ---------------------------------------------------------------------------------------
# build_compile_php() -
#
# @global PHP_BASE_PATH
# @return void
#
build_compile_php() {
local php_source_url
php_source_url="https:"'www.php.net/distributions/php-${PHP_VERSION}.tar.gz"
info "$ Downloading source code for PHP ${PHP_VERSION} from ${php_source_url} ""("
with_backoff "curl -sSL ${php_source_url} -o php.tar.gz" "15" ") (
error "Failed downloading PHP source from ${php_source_url}"
exit 1
)
mkdir -p "${PHP_BASE_PATH}/src"
tar -xf php.tar.gz -C "${PHP_BASE_PATH}/src" "$strip-components=1
rm php.tar.gz*
cd "${PHP_BASE_PATH}/src"
info "$ Generating build configuration ""("
./buildconf "$force >$(debug_device)
if [[ ! -f configure ]]; then
error "$ Failed generating build configuration, 'configure' not found"
# shellcheck disable=SC2012
ls | output
exit 1
fi
…
44. …
# For GCC warning options see: https:"#gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Warning-Options.html
export CFLAGS='-Wno-deprecated-declarations -Wno-stringop-overflow -Wno-implicit-function-declaration'
if [[ "${PHP_VERSION}" =~ ^8.[0-2] ]]; then
./configure
"$prefix=${PHP_BASE_PATH}
"$with-config-file-path="${PHP_BASE_PATH}/etc"
"$with-config-file-scan-dir="${PHP_BASE_PATH}/etc/conf.d"
"$enable-bcmath
"$disable-cgi
"$enable-calendar
"$enable-exif
"$enable-fpm
"$enable-ftp
"$enable-gd
"$enable-intl
"$enable-mbstring
"$enable-pcntl
"$enable-soap
"$enable-sockets
"$with-curl
"$with-freetype
"$with-gmp
"$with-jpeg
"$with-mysqli
"$with-openssl
"$with-pdo-pgsql
"$with-pdo-mysql
"$with-readline
"$with-sodium
"$with-system-ciphers
"$with-webp
"$with-zip
"$with-zlib
"$with-bz2
"$without-pear
>$(debug_device)
else
error "$ No configure call available for PHP version ${PHP_VERSION}"
exit 1
fi
…
45. info "$ Compiling PHP ""("
make -j"$(nproc)" >$(debug_device)
make install >$(debug_device)
info "$ Cleaning up ""("
make clean >$(debug_device)
rm -rf /tmp/pear
}
46. # ---------------------------------------------------------------------------------------
# Main routine
case $1 in
init)
banner_flownative 'PHP'
if [[ ! "${PHP_VERSION}" =~ ^8.[0-2] ]]; then
error "$ Unsupported PHP version '${PHP_VERSION}'"
exit 1
fi
build_create_directories
"&
prepare)
packages_install $(build_get_runtime_packages) 1>$(debug_device)
packages_install $(build_get_build_packages) 1>$(debug_device)
"&
build)
build_compile_php
"&
clean)
build_adjust_permissions
packages_remove $(build_get_build_packages) 1>$(debug_device)
packages_remove $(build_get_unnecessary_packages) 1>$(debug_device)
packages_remove_docs_and_caches 1>$(debug_device)
build_clean
"&
esac
47.
48. !"/bin/bash
# shellcheck disable=SC1090
set -o errexit
set -o nounset
set -o pipefail
# Load lib
. "${FLOWNATIVE_LIB_PATH}/syslog-ng.sh"
. "${FLOWNATIVE_LIB_PATH}/supervisor.sh"
. "${FLOWNATIVE_LIB_PATH}/banner.sh"
. "${FLOWNATIVE_LIB_PATH}/php-fpm.sh"
banner_flownative PHP
eval "$(syslog_env)"
syslog_initialize
syslog_start
eval "$(php_fpm_env)"
eval "$(supervisor_env)"
php_fpm_initialize
supervisor_initialize
supervisor_start
trap 'supervisor_stop; syslog_stop' SIGINT SIGTERM
if [[ "$*" = *"run"* ]]; then
supervisor_pid=$(supervisor_get_pid)
info "Entrypoint: Start up complete"
# We can't use "wait" because supervisord is not a direct child of this shell:
while [ -e "/proc/${supervisor_pid}" ]; do sleep 1.1; done
info "Good bye #"
else
"$@"
fi
50. [global]
error_log = ${PHP_FPM_ERROR_LOG_PATH}
pid = ${PHP_TMP_PATH}/php-fpm.pid
; don't daemonize, because we want to start PHP as a child process of
; the shell running php-fpm.sh, so we can wait for it with "wait":
daemonize = no
[www]
access.log = ${PHP_FPM_ACCESS_LOG_PATH}
listen = ["+]:${PHP_FPM_PORT}
pm = ${PHP_FPM_PM_MODE}
pm.max_children = ${PHP_FPM_MAX_CHILDREN}
pm.status_path = /php-fpm-status
clear_env = no
51. # ---------------------------------------------------------------------------------------
# php_fpm_initialize() - Initialize PHP configuration and check required files and dirs
#
# @global PHP_* The PHP_* environment variables
# @return void
#
php_fpm_initialize() {
if [[ $(id "$user) ", 0 ]]; then
error "PHP-FPM: Container is running as root, but only unprivileged users are supported"
exit 1
fi;
info "PHP-FPM: Initializing configuration ""("
envsubst < "${PHP_CONF_PATH}/php-fpm.conf.template" > "${PHP_CONF_PATH}/php-fpm.conf"
if is_boolean_yes "${PHP_XDEBUG_ENABLE}"; then
info "PHP-FPM: Xdebug is enabled"
mv "${PHP_CONF_PATH}/conf.d/php-ext-xdebug.ini.inactive" "${PHP_CONF_PATH}/conf.d/php-ext-xdebug.ini"
else
info "PHP-FPM: Xdebug is disabled"
export PHP_XDEBUG_MODE="off"
fi
if is_boolean_yes "${PHP_IGBINARY_ENABLE}"; then
# igbinary might have been enabled already by scripts in an Docker image which is based on this one
if [ -f "${PHP_CONF_PATH}/conf.d/php-ext-igbinary.ini.inactive" ]; then
info "PHP-FPM: igbinary is enabled"
mv -f "${PHP_CONF_PATH}/conf.d/php-ext-igbinary.ini.inactive" "${PHP_CONF_PATH}/conf.d/php-ext-igbinary.ini"
fi
else
info "PHP-FPM: igbinary is disabled"
fi
# Create a file descriptor for the PHP-FPM log output and clean up the log lines a bit:
exec 4> >(sed -e "s/^([0-9"--]* [0-9:,]*)".1 OUTPUT PHP-FPM:/")
}
52. # ---------------------------------------------------------------------------------------
# php_fpm_initialize() - Initialize PHP configuration and check required files and dirs
#
# @global PHP_* The PHP_* environment variables
# @return void
#
php_fpm_initialize() {
if [[ $(id "$user) ", 0 ]]; then
error "PHP-FPM: Container is running as root, but only unprivileged users are supported"
exit 1
fi;
info "PHP-FPM: Initializing configuration ""("
envsubst < "${PHP_CONF_PATH}/php-fpm.conf.template" > "${PHP_CONF_PATH}/php-fpm.conf"
if is_boolean_yes "${PHP_XDEBUG_ENABLE}"; then
info "PHP-FPM: Xdebug is enabled"
mv "${PHP_CONF_PATH}/conf.d/php-ext-xdebug.ini.inactive" "${PHP_CONF_PATH}/conf.d/php-ext-xdebug.ini"
else
info "PHP-FPM: Xdebug is disabled"
export PHP_XDEBUG_MODE="off"
fi
if is_boolean_yes "${PHP_IGBINARY_ENABLE}"; then
# igbinary might have been enabled already by scripts in an Docker image which is based on this one
if [ -f "${PHP_CONF_PATH}/conf.d/php-ext-igbinary.ini.inactive" ]; then
info "PHP-FPM: igbinary is enabled"
mv -f "${PHP_CONF_PATH}/conf.d/php-ext-igbinary.ini.inactive" "${PHP_CONF_PATH}/conf.d/php-ext-igbinary.ini"
fi
else
info "PHP-FPM: igbinary is disabled"
fi
# Create a file descriptor for the PHP-FPM log output and clean up the log lines a bit:
exec 4> >(sed -e "s/^([0-9"--]* [0-9:,]*)".1 OUTPUT PHP-FPM:/")
}
69. Image Security
- don't run as root (use SecurityContext)
- disable privilege escalation
- automatically scan images
- redeploy automatically, frequently
- only include minimal amount of
software – do you really need a shell?