more docs and rework

This commit is contained in:
Christian Winther 2024-01-04 20:55:04 +00:00
parent e05575283a
commit a08a5e7cde
10 changed files with 226 additions and 87 deletions

View file

@ -5,14 +5,29 @@
# Configuration # Configuration
####################################################### #######################################################
# See: https://github.com/composer/composer/releases
ARG COMPOSER_VERSION="2.6" ARG COMPOSER_VERSION="2.6"
# See: https://nginx.org/
ARG NGINX_VERSION=1.25.3 ARG NGINX_VERSION=1.25.3
# See: https://github.com/ddollar/forego
ARG FOREGO_VERSION=0.17.2 ARG FOREGO_VERSION=0.17.2
# See: https://github.com/hairyhenderson/gomplate
ARG GOMPLATE_VERSION=v3.11.6
###
# PHP base configuration # PHP base configuration
###
# See: https://hub.docker.com/_/php/tags
ARG PHP_VERSION="8.1" ARG PHP_VERSION="8.1"
# See: https://github.com/docker-library/docs/blob/master/php/README.md#image-variants
ARG PHP_BASE_TYPE="apache" ARG PHP_BASE_TYPE="apache"
ARG PHP_DEBIAN_RELEASE="bullseye" ARG PHP_DEBIAN_RELEASE="bullseye"
ARG RUNTIME_UID=33 # often called 'www-data' ARG RUNTIME_UID=33 # often called 'www-data'
ARG RUNTIME_GID=33 # often called 'www-data' ARG RUNTIME_GID=33 # often called 'www-data'
@ -57,17 +72,31 @@ FROM nginx:${NGINX_VERSION} AS nginx-image
# See: https://github.com/nginx-proxy/forego # See: https://github.com/nginx-proxy/forego
FROM nginxproxy/forego:${FOREGO_VERSION}-debian AS forego-image FROM nginxproxy/forego:${FOREGO_VERSION}-debian AS forego-image
# gomplate-image grabs the gomplate binary from GitHub releases
#
# It's in its own layer so it can be fetched in parallel with other build steps
FROM php:${PHP_VERSION}-${PHP_BASE_TYPE}-${PHP_DEBIAN_RELEASE} AS gomplate-image
ARG BUILDARCH
ARG BUILDOS
ARG GOMPLATE_VERSION
RUN set -ex \
&& curl --silent --show-error --location --output /usr/local/bin/gomplate https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${BUILDOS}-${BUILDARCH} \
&& chmod +x /usr/local/bin/gomplate \
&& /usr/local/bin/gomplate --version
####################################################### #######################################################
# Base image # Base image
####################################################### #######################################################
FROM php:${PHP_VERSION}-${PHP_BASE_TYPE}-${PHP_DEBIAN_RELEASE} AS base FROM php:${PHP_VERSION}-${PHP_BASE_TYPE}-${PHP_DEBIAN_RELEASE} AS base
ARG PHP_VERSION
ARG PHP_DEBIAN_RELEASE
ARG APT_PACKAGES_EXTRA ARG APT_PACKAGES_EXTRA
ARG RUNTIME_UID ARG PHP_DEBIAN_RELEASE
ARG PHP_VERSION
ARG RUNTIME_GID ARG RUNTIME_GID
ARG RUNTIME_UID
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG BUILDKIT_SBOM_SCAN_STAGE=true ARG BUILDKIT_SBOM_SCAN_STAGE=true
@ -173,8 +202,11 @@ USER root:root
FROM base AS shared-runtime FROM base AS shared-runtime
ARG RUNTIME_UID ARG BUILDARCH
ARG BUILDOS
ARG GOMPLATE_VERSION
ARG RUNTIME_GID ARG RUNTIME_GID
ARG RUNTIME_UID
ENV RUNTIME_UID=${RUNTIME_UID} ENV RUNTIME_UID=${RUNTIME_UID}
ENV RUNTIME_GID=${RUNTIME_GID} ENV RUNTIME_GID=${RUNTIME_GID}
@ -183,6 +215,7 @@ COPY --link --from=php-extensions /usr/local/lib/php/extensions /usr/local/lib/p
COPY --link --from=php-extensions /usr/local/etc/php /usr/local/etc/php COPY --link --from=php-extensions /usr/local/etc/php /usr/local/etc/php
COPY --link --from=composer-and-src --chown=${RUNTIME_UID}:${RUNTIME_GID} /var/www /var/www COPY --link --from=composer-and-src --chown=${RUNTIME_UID}:${RUNTIME_GID} /var/www /var/www
COPY --link --from=forego-image /usr/local/bin/forego /usr/local/bin/forego COPY --link --from=forego-image /usr/local/bin/forego /usr/local/bin/forego
COPY --link --from=gomplate-image /usr/local/bin/gomplate /usr/local/bin/gomplate
# for detail why storage is copied this way, pls refer to https://github.com/pixelfed/pixelfed/pull/2137#discussion_r434468862 # for detail why storage is copied this way, pls refer to https://github.com/pixelfed/pixelfed/pull/2137#discussion_r434468862
RUN set -ex \ RUN set -ex \

View file

@ -93,6 +93,72 @@ services:
PHP_BASE_TYPE: fpm PHP_BASE_TYPE: fpm
``` ```
## Customizing your `Dockerfile`
### Running commands on container start
#### Description
When a Pixelfed container starts up, the [`ENTRYPOINT`](https://docs.docker.com/engine/reference/builder/#entrypoint) script will
1. Search the `/docker/entrypoint.d/` directory for files and for each file (in lexical order).
1. Check if the file is executable.
1. If the file is not executable, print an error and exit the container.
1. If the file has the extension `.envsh` the file will be [sourced](https://superuser.com/a/46146).
1. If the file has the extension `.sh` the file will be run like a normal script.
1. Any other file extension will log a warning and will be ignored.
#### Included scripts
* `/docker/entrypoint.d/04-defaults.envsh` calculates Docker container environment variables needed for [templating](#templating) configuration files.
* `/docker/entrypoint.d/05-templating.sh` renders [template](#templating) configuration files.
* `/docker/entrypoint.d/10-storage.sh` ensures Pixelfed storage related permissions and commands are run.
* `/docker/entrypoint.d/20-horizon.sh` ensures [Laravel Horizon](https://laravel.com/docs/master/horizon) used by Pixelfed is configured
* `/docker/entrypoint.d/30-cache.sh` ensures all Pixelfed caches (router, view, config) is warmed
#### Disabling entrypoint or individual scripts
To disable the entire entrypoint you can set the variable `ENTRYPOINT_SKIP=1`.
To disable individual entrypoint scripts you can add the filename to the space (`" "`) separated variable `ENTRYPOINT_SKIP_SCRIPTS`. (example: `ENTRYPOINT_SKIP_SCRIPTS="10-storage.sh 30-cache.sh"`)
### Templating
The Docker container can do some basic templating (more like variable replacement) as part of the entrypoint scripts via [gomplate](https://docs.gomplate.ca/).
Any file put in the `/docker/templates/` directory will be templated and written to the right directory.
#### File path examples
1. To template `/usr/local/etc/php/php.ini` in the container put the source file in `/docker/templates/usr/local/etc/php/php.ini`.
1. To template `/a/fantastic/example.txt` in the container put the source file in `/docker/templates/a/fantastic/example.txt`.
1. To template `/some/path/anywhere` in the container put the source file in `/docker/templates/a/fantastic/example.txt`.
#### Available variables
Variables available for templating are sourced (in order, so *last* source takes precedence) like this:
1. `env:` in your `docker-compose.yml` or `-e` in your `docker run` / `docker compose run`
1. Any exported variables in `.envsh` files loaded *before* `05-templating.sh` (e.g. any file with `04-`, `03-`, `02-`, `01-` or `00-` prefix)
1. All key/value pairs in `/var/www/.env.docker`
1. All key/value pairs in `/var/www/.env`
#### Template guide 101
Please see the [gomplate documentation](https://docs.gomplate.ca/) for a more comprehensive overview.
The most frequent use-case you have is likely to print a environment variable (or a default value if it's missing), so this is how to do that:
* `{{ getenv "VAR_NAME" }}` print an environment variable and **fail** if the variable is not set. ([docs](https://docs.gomplate.ca/functions/env/#envgetenv))
* `{{ getenv "VAR_NAME" "default" }}` print an environment variable and print `default` if the variable is not set. ([docs](https://docs.gomplate.ca/functions/env/#envgetenv))
The script will *fail* if you reference a variable that does not exist (and don't have a default value) in a template.
Please see the
* [gomplate syntax documentation](https://docs.gomplate.ca/syntax/)
* [gomplate functions documentation](https://docs.gomplate.ca/functions/)
## Build settings (arguments) ## Build settings (arguments)
The Pixelfed Dockerfile utilizes [Docker Multi-stage builds](https://docs.docker.com/build/building/multi-stage/) and [Build arguments](https://docs.docker.com/build/guide/build-args/). The Pixelfed Dockerfile utilizes [Docker Multi-stage builds](https://docs.docker.com/build/building/multi-stage/) and [Build arguments](https://docs.docker.com/build/guide/build-args/).

View file

@ -1,7 +1,7 @@
server { server {
listen 80 default_server; listen 80 default_server;
server_name ${APP_DOMAIN}; server_name {{ getenv "APP_DOMAIN" }};
root /var/www/public; root /var/www/public;
add_header X-Frame-Options "SAMEORIGIN"; add_header X-Frame-Options "SAMEORIGIN";
@ -14,7 +14,7 @@ server {
index index.html index.htm index.php; index index.html index.htm index.php;
charset utf-8; charset utf-8;
client_max_body_size ${POST_MAX_SIZE}; client_max_body_size {{ getenv "POST_MAX_SIZE" }};
location / { location / {
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;

View file

@ -3,38 +3,37 @@ source /docker/helpers.sh
set_identity "$0" set_identity "$0"
auto_envsubst() { declare template_dir="${ENVSUBST_TEMPLATE_DIR:-/docker/templates}"
local template_dir="${ENVSUBST_TEMPLATE_DIR:-/docker/templates}" declare output_dir="${ENVSUBST_OUTPUT_DIR:-}"
local output_dir="${ENVSUBST_OUTPUT_DIR:-}" declare filter="${ENVSUBST_FILTER:-}"
local filter="${ENVSUBST_FILTER:-}" declare template defined_envs relative_path output_path output_dir subdir
local template defined_envs relative_path output_path output_dir subdir
# load all dot-env files # load all dot-env files
load-config-files load-config-files
# export all dot-env variables so they are available in templating : ${ENTRYPOINT_SHOW_TEMPLATE_DIFF:=1}
export ${seen_dot_env_variables[@]}
defined_envs=$(printf '${%s} ' $(awk "END { for (name in ENVIRON) { print ( name ~ /${filter}/ ) ? name : \"\" } }" </dev/null)) # export all dot-env variables so they are available in templating
export ${seen_dot_env_variables[@]}
find "$template_dir" -follow -type f -print | while read -r template; do find "$template_dir" -follow -type f -print | while read -r template; do
relative_path="${template#"$template_dir/"}" relative_path="${template#"$template_dir/"}"
subdir=$(dirname "$relative_path") subdir=$(dirname "$relative_path")
output_path="$output_dir/${relative_path}" output_path="$output_dir/${relative_path}"
output_dir=$(dirname "$output_path") output_dir=$(dirname "$output_path")
if [ ! -w "$output_dir" ]; then if [ ! -w "$output_dir" ]; then
log_error_and_exit "ERROR: $template_dir exists, but $output_dir is not writable" log_error_and_exit "ERROR: $template_dir exists, but $output_dir is not writable"
fi fi
# create a subdirectory where the template file exists # create a subdirectory where the template file exists
mkdir -p "$output_dir/$subdir" mkdir -p "$output_dir/$subdir"
log "Running envsubst on $template to $output_path" log "Running [gomplate] on [$template] --> [$output_path]"
envsubst "$defined_envs" <"$template" >"$output_path" cat "$template" | gomplate >"$output_path"
done
}
auto_envsubst # Show the diff from the envsubst command
if [[ ${ENTRYPOINT_SHOW_TEMPLATE_DIFF} = 1 ]]; then
exit 0 git --no-pager diff "$template" "${output_path}" || :
fi
done

View file

@ -3,10 +3,7 @@ source /docker/helpers.sh
set_identity "$0" set_identity "$0"
log "Create the storage tree if needed"
as_runtime_user cp --recursive storage.skel/* storage/ as_runtime_user cp --recursive storage.skel/* storage/
log "Ensure storage is linked"
as_runtime_user php artisan storage:link as_runtime_user php artisan storage:link
log "Ensure permissions are correct" log "Ensure permissions are correct"

View file

@ -3,11 +3,6 @@ source /docker/helpers.sh
set_identity "$0" set_identity "$0"
log "==> route:cache"
as_runtime_user php artisan route:cache as_runtime_user php artisan route:cache
log "==> view:cache"
as_runtime_user php artisan view:cache as_runtime_user php artisan view:cache
log "==> config:cache"
as_runtime_user php artisan config:cache as_runtime_user php artisan config:cache

View file

@ -1,50 +1,71 @@
#!/bin/bash #!/bin/bash
set -e -o errexit -o nounset -o pipefail set -e -o errexit -o nounset -o pipefail
[[ -n ${ENTRYPOINT_DEBUG:-} ]] && set -x : ${ENTRYPOINT_SKIP:=0}
: ${ENTRYPOINT_SKIP_SCRIPTS:=""}
: ${ENTRYPOINT_DEBUG:=0}
: ${ENTRYPOINT_ROOT:="/docker/entrypoint.d/"}
declare -g ME="$0" export ENTRYPOINT_ROOT
declare -gr ENTRYPOINT_ROOT=/docker/entrypoint.d/
source /docker/helpers.sh if [[ ${ENTRYPOINT_SKIP} == 0 ]]; then
[[ ${ENTRYPOINT_DEBUG} == 1 ]] && set -x
# ensure the entrypoint folder exists source /docker/helpers.sh
mkdir -p "${ENTRYPOINT_ROOT}"
if /usr/bin/find "${ENTRYPOINT_ROOT}" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then declare -a skip_scripts=()
log "looking for shell scripts in /docker/entrypoint.d/" IFS=' ' read -a skip_scripts <<<"$ENTRYPOINT_SKIP_SCRIPTS"
find "${ENTRYPOINT_ROOT}" -follow -type f -print | sort -V | while read -r f; do
case "$f" in declare script_name
*.envsh)
if [ -x "$f" ]; then # ensure the entrypoint folder exists
log "Sourcing $f" mkdir -p "${ENTRYPOINT_ROOT}"
source "$f"
resetore_identity if /usr/bin/find "${ENTRYPOINT_ROOT}" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
else log "looking for shell scripts in /docker/entrypoint.d/"
# warn on shell scripts without exec bit
log_warning "Ignoring $f, not executable" find "${ENTRYPOINT_ROOT}" -follow -type f -print | sort -V | while read -r f; do
script_name="$(get_script_name $f)"
if array_value_exists skip_scripts "${script_name}"; then
log_warning "Skipping script [${script_name}] since it's in the skip list (\$ENTRYPOINT_SKIP_SCRIPTS)"
continue
fi fi
;;
*.sh) case "$f" in
if [ -x "$f" ]; then *.envsh)
log "Launching $f" if [ -x "$f" ]; then
"$f" log "Sourcing $f"
else
# warn on shell scripts without exec bit
log_warning "Ignoring $f, not executable"
fi
;;
*) source "$f"
log_warning "Ignoring $f"
;;
esac
done
log "Configuration complete; ready for start up" resetore_identity
else else
log_warning "No files found in ${ENTRYPOINT_ROOT}, skipping configuration" # warn on shell scripts without exec bit
log_error_and_exit "File [$f] is not executable (please 'chmod +x' it)"
fi
;;
*.sh)
if [ -x "$f" ]; then
log "Launching $f"
"$f"
else
# warn on shell scripts without exec bit
log_error_and_exit "File [$f] is not executable (please 'chmod +x' it)"
fi
;;
*)
log_warning "Ignoring $f"
;;
esac
done
log "Configuration complete; ready for start up"
else
log_warning "No files found in ${ENTRYPOINT_ROOT}, skipping configuration"
fi
fi fi
exec "$@" exec "$@"

View file

@ -10,11 +10,11 @@ declare -ra dot_env_files=(
/var/www/.env.docker /var/www/.env.docker
/var/www/.env /var/www/.env
) )
declare -a seen_dot_env_variables=() declare -ga seen_dot_env_variables=()
function set_identity() { function set_identity() {
old_log_prefix="${log_prefix}" old_log_prefix="${log_prefix}"
log_prefix="ENTRYPOINT - [${1}] - " log_prefix="ENTRYPOINT - [$(get_script_name $1)] - "
} }
function resetore_identity() { function resetore_identity() {
@ -22,7 +22,23 @@ function resetore_identity() {
} }
function as_runtime_user() { function as_runtime_user() {
su --preserve-environment $(id -un ${RUNTIME_UID}) --shell /bin/bash --command "${*}" local -i exit_code
local target_user
target_user=$(id -un ${RUNTIME_UID})
log "👷 Running [${*}] as [${target_user}]"
su --preserve-environment "${target_user}" --shell /bin/bash --command "${*}"
exit_code=$?
if [[ $exit_code != 0 ]]; then
log_error "❌ Error!"
return $exit_code
fi
log "✅ OK!"
return $exit_code
} }
# @description Display the given error message with its line number on stderr and exit with error. # @description Display the given error message with its line number on stderr and exit with error.
@ -53,7 +69,7 @@ function log() {
} }
function load-config-files() { function load-config-files() {
# Associative array (aka map/disctrionary) holding the unique keys found in dot-env files # Associative array (aka map/dictionary) holding the unique keys found in dot-env files
local -A _tmp_dot_env_keys local -A _tmp_dot_env_keys
for f in "${dot_env_files[@]}"; do for f in "${dot_env_files[@]}"; do
@ -73,3 +89,14 @@ function load-config-files() {
seen_dot_env_variables=(${!_tmp_dot_env_keys[@]}) seen_dot_env_variables=(${!_tmp_dot_env_keys[@]})
} }
function array_value_exists() {
local -nr validOptions=$1
local -r providedValue="\<${2}\>"
[[ ${validOptions[*]} =~ $providedValue ]]
}
function get_script_name() {
echo "${1#"$ENTRYPOINT_ROOT"}"
}

View file

@ -15,7 +15,7 @@ echo 'APT::Install-Suggests "false";' >>/etc/apt/apt.conf
declare -ra standardPackages=( declare -ra standardPackages=(
apt-utils apt-utils
ca-certificates ca-certificates
gettext-base curl
git git
gnupg1 gnupg1
gosu gosu
@ -25,9 +25,10 @@ declare -ra standardPackages=(
locales-all locales-all
nano nano
procps procps
unzip
zip
software-properties-common software-properties-common
unzip
wget
zip
) )
# Image Optimization # Image Optimization

View file

@ -679,7 +679,7 @@ auto_globals_jit = On
; Its value may be 0 to disable the limit. It is ignored if POST data reading ; Its value may be 0 to disable the limit. It is ignored if POST data reading
; is disabled through enable_post_data_reading. ; is disabled through enable_post_data_reading.
; http://php.net/post-max-size ; http://php.net/post-max-size
post_max_size = ${POST_MAX_SIZE} post_max_size = {{ getenv "POST_MAX_SIZE" }}
; Automatically add files before PHP document. ; Automatically add files before PHP document.
; http://php.net/auto-prepend-file ; http://php.net/auto-prepend-file
@ -831,10 +831,10 @@ file_uploads = On
; Maximum allowed size for uploaded files. ; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize ; http://php.net/upload-max-filesize
upload_max_filesize = ${POST_MAX_SIZE} upload_max_filesize = {{ getenv "POST_MAX_SIZE" }}
; Maximum number of files that can be uploaded via a single request ; Maximum number of files that can be uploaded via a single request
max_file_uploads = ${MAX_ALBUM_LENGTH} max_file_uploads = {{ getenv "MAX_ALBUM_LENGTH" }}
;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;
; Fopen wrappers ; ; Fopen wrappers ;