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
#######################################################
# See: https://github.com/composer/composer/releases
ARG COMPOSER_VERSION="2.6"
# See: https://nginx.org/
ARG NGINX_VERSION=1.25.3
# See: https://github.com/ddollar/forego
ARG FOREGO_VERSION=0.17.2
# See: https://github.com/hairyhenderson/gomplate
ARG GOMPLATE_VERSION=v3.11.6
###
# PHP base configuration
###
# See: https://hub.docker.com/_/php/tags
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_DEBIAN_RELEASE="bullseye"
ARG RUNTIME_UID=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
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
#######################################################
FROM php:${PHP_VERSION}-${PHP_BASE_TYPE}-${PHP_DEBIAN_RELEASE} AS base
ARG PHP_VERSION
ARG PHP_DEBIAN_RELEASE
ARG APT_PACKAGES_EXTRA
ARG RUNTIME_UID
ARG PHP_DEBIAN_RELEASE
ARG PHP_VERSION
ARG RUNTIME_GID
ARG RUNTIME_UID
ARG TARGETPLATFORM
ARG BUILDKIT_SBOM_SCAN_STAGE=true
@ -173,8 +202,11 @@ USER root:root
FROM base AS shared-runtime
ARG RUNTIME_UID
ARG BUILDARCH
ARG BUILDOS
ARG GOMPLATE_VERSION
ARG RUNTIME_GID
ARG RUNTIME_UID
ENV RUNTIME_UID=${RUNTIME_UID}
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=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=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
RUN set -ex \

View file

@ -93,6 +93,72 @@ services:
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)
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 {
listen 80 default_server;
server_name ${APP_DOMAIN};
server_name {{ getenv "APP_DOMAIN" }};
root /var/www/public;
add_header X-Frame-Options "SAMEORIGIN";
@ -14,7 +14,7 @@ server {
index index.html index.htm index.php;
charset utf-8;
client_max_body_size ${POST_MAX_SIZE};
client_max_body_size {{ getenv "POST_MAX_SIZE" }};
location / {
try_files $uri $uri/ /index.php?$query_string;

View file

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

View file

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

View file

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

View file

@ -1,50 +1,71 @@
#!/bin/bash
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"
declare -gr ENTRYPOINT_ROOT=/docker/entrypoint.d/
export ENTRYPOINT_ROOT
source /docker/helpers.sh
if [[ ${ENTRYPOINT_SKIP} == 0 ]]; then
[[ ${ENTRYPOINT_DEBUG} == 1 ]] && set -x
# ensure the entrypoint folder exists
mkdir -p "${ENTRYPOINT_ROOT}"
source /docker/helpers.sh
if /usr/bin/find "${ENTRYPOINT_ROOT}" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
log "looking for shell scripts in /docker/entrypoint.d/"
find "${ENTRYPOINT_ROOT}" -follow -type f -print | sort -V | while read -r f; do
case "$f" in
*.envsh)
if [ -x "$f" ]; then
log "Sourcing $f"
source "$f"
resetore_identity
else
# warn on shell scripts without exec bit
log_warning "Ignoring $f, not executable"
declare -a skip_scripts=()
IFS=' ' read -a skip_scripts <<<"$ENTRYPOINT_SKIP_SCRIPTS"
declare script_name
# ensure the entrypoint folder exists
mkdir -p "${ENTRYPOINT_ROOT}"
if /usr/bin/find "${ENTRYPOINT_ROOT}" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
log "looking for shell scripts in /docker/entrypoint.d/"
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
;;
*.sh)
if [ -x "$f" ]; then
log "Launching $f"
"$f"
else
# warn on shell scripts without exec bit
log_warning "Ignoring $f, not executable"
fi
;;
case "$f" in
*.envsh)
if [ -x "$f" ]; then
log "Sourcing $f"
*)
log_warning "Ignoring $f"
;;
esac
done
source "$f"
log "Configuration complete; ready for start up"
else
log_warning "No files found in ${ENTRYPOINT_ROOT}, skipping configuration"
resetore_identity
else
# 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
exec "$@"

View file

@ -10,11 +10,11 @@ declare -ra dot_env_files=(
/var/www/.env.docker
/var/www/.env
)
declare -a seen_dot_env_variables=()
declare -ga seen_dot_env_variables=()
function set_identity() {
old_log_prefix="${log_prefix}"
log_prefix="ENTRYPOINT - [${1}] - "
log_prefix="ENTRYPOINT - [$(get_script_name $1)] - "
}
function resetore_identity() {
@ -22,7 +22,23 @@ function resetore_identity() {
}
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.
@ -53,7 +69,7 @@ function log() {
}
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
for f in "${dot_env_files[@]}"; do
@ -73,3 +89,14 @@ function load-config-files() {
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=(
apt-utils
ca-certificates
gettext-base
curl
git
gnupg1
gosu
@ -25,9 +25,10 @@ declare -ra standardPackages=(
locales-all
nano
procps
unzip
zip
software-properties-common
unzip
wget
zip
)
# 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
; is disabled through enable_post_data_reading.
; 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.
; http://php.net/auto-prepend-file
@ -831,10 +831,10 @@ file_uploads = On
; Maximum allowed size for uploaded files.
; 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
max_file_uploads = ${MAX_ALBUM_LENGTH}
max_file_uploads = {{ getenv "MAX_ALBUM_LENGTH" }}
;;;;;;;;;;;;;;;;;;
; Fopen wrappers ;