This article shows you how to containerize PHP applications like DokuWiki.

Compared to other software, DokuWiki has many advantages: it is easy to install and use, has low resource usage, and can be used independently of traditional databases.

Writing in the front

You may be wondering why containerization is so “easy”, but back in early 2019, I wrote an article called “Build your Wiki with Docker” that explained how to quickly start services using containers.

There are two reasons.

The article, using the mirror image of the BitNami problems for the application of data processing, users can’t targeted only to mount a certain, certain directory, and all data must be mounted, it is very bad for data storage, and the image is larger than I rebuild the image of a times, resource use efficiency is relatively low.

PHP project for the container packaging, before the introduction of relatively little, but in the daily work, learning process, PHP project number is quite a lot, just take this opportunity to talk about.

Select the container runtime environment

On the official website, we can see that DokuWiki has two clear product release lines: the stable version and the upcoming version www.dokuwiki.org/changes.

One important clue to know before wrapping a container is what version of the Runtime the software depends on.

The current version of DokuWiki has some PHP 7.4 compatibility, while future versions will drop support for PHP 7.2 in favor of PHP 8. One other thing we need to keep in mind is plug-in compatibility. Often, many plug-ins are written for only one version of the application, and without maintenance, it is difficult to ensure compatibility with the language version unless the language is always forward compatible.

Therefore, the safe choice here is to use EITHER PHP 7.3 or PHP 7.4 as the runtime environment. For this article, considering the process of compiling and installing the plug-in and the final image size, I chose PHP 7.3, a “back and forth” version.

In addition, in the official GitHub submission, the maintainers have added a version test for PHP 8, and the CI process seems to be all right. We believe that a new version will be released soon. When a new version is released in the future, we can use PHP 7.3 first and then upgrade to PHP8 after the relevant plug-ins are upgraded.

Write container image files

Image encapsulation of PHP applications consists of the following parts: runtime environment selection, base environment configuration, application and application dependent installation, application default configuration Settings, and configuration startup entry.

Let’s talk about it one by one.

Operating environment selection

A common scenario for choosing a container runtime environment is between the FPM and Apache versions, and depending on the system distribution, the former means that you want to use an external Web Server (such as Nginx) to provide Web responsiveness, fine-grained tuning and control. Choosing the latter means “saving trouble”, which is not consistent with the container single-process philosophy, but in the face of the huge number of downloads of such PHP images, the “philosophy” needs to respect the objective requirements.

Here, too, we choose the “easy” option:

The FROM PHP: 7.3 apacheCopy the code

Basic Environment Configuration

The basic environment configuration consists of three parts: system basic software configuration and installation, system-level module or software dependency installation, and PHP system parameter configuration.

In addition to that, for some reason, existing in domestic download overseas packages at a slower speed, in order to improve construction speed, we usually configuration domestic mirror to accelerate, of course, the pursuit of perfection, you can also use the before mentioned in the article “using a container building APT Cacher NG caching proxy service” approach, Further optimize the speed of software package download.

ENV DEBIAN_FRONTEND=noninteractive

RUN set -eux; \
    sed -i -e "s/security.debian.org/mirrors.tuna.tsinghua.edu.cn/" /etc/apt/sources.list; \
    sed -i -e "s/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/" /etc/apt/sources.list; \
    apt update; \
    apt-get update --fix-missing; \
    apt install -y tzdata curl wget; \
    rm -rf /var/lib/apt/lists/*
Copy the code

With the above configuration, the subsequent image building process will use Tsinghua source for dependency download, which will greatly reduce the time cost of building.

For the base module installation, I used a WordPress code snippet from the official Docker repository (with minor modifications).

RUN set -ex; \
	\
	savedAptMark="$(apt-mark showmanual)"; \
	\
	apt-get update; \
	apt-get install -y --no-install-recommends \
		libfreetype6-dev \
		libjpeg-dev \
		libmagickwand-dev \
		libpng-dev \
		libzip-dev \
	; \
	\
	docker-php-ext-configure gd \
		--with-freetype-dir=/usr \
		--with-jpeg-dir=/usr \
		--with-png-dir=/usr \
	; \
	docker-php-ext-install -j "$(nproc)"\ bcmath \ exif \ gd \ zip \ ; \ pecl install imagick - 3.4.4; \ docker-php-ext-enable imagick; \ rm -r /tmp/pear; \ \# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
	apt-mark auto '*' > /dev/null; \
	apt-mark manual $savedAptMark; \
	ldd "$(php -r 'echo ini_get("extension_dir"); ')"/*.so \
		| awk '/=>/ { print $3 }' \
		| sort -u \
		| xargs -r dpkg-query -S \
		| cut -d: -f1 \
		| sort -u \
		| xargs -rt apt-mark manual; \
	\
	apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
	rm -rf /var/lib/apt/lists/*

# set recommended PHP.ini settings
# see https://secure.php.net/manual/en/opcache.installation.php
RUN set-eux; \ docker-php-ext-enable opcache; \ {\echo 'opcache.memory_consumption=128'; \
		echo 'opcache.interned_strings_buffer=8'; \
		echo 'opcache.max_accelerated_files=4000'; \
		echo 'opcache.revalidate_freq=2'; \
		echo 'opcache.fast_shutdown=1'; \
	} > /usr/local/etc/php/conf.d/opcache-recommended.ini
# https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging

RUN { \
# https://www.php.net/manual/en/errorfunc.constants.php
# https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670
		echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING |  E_RECOVERABLE_ERROR'; \
		echo 'display_errors = Off'; \
		echo 'display_startup_errors = Off'; \
		echo 'log_errors = On'; \
		echo 'error_log = /dev/stderr'; \
		echo 'log_errors_max_len = 1024'; \
		echo 'ignore_repeated_errors = On'; \
		echo 'ignore_repeated_source = Off'; \
		echo 'html_errors = Off'; \
	} > /usr/local/etc/php/conf.d/error-logging.ini

RUN set -eux; \
	a2enmod rewrite expires; \
	\
# https://httpd.apache.org/docs/2.4/mod/mod_remoteip.htmla2enmod remoteip; \ {\echo 'RemoteIPHeader X-Forwarded-For'; \
# these IP ranges are reserved for "private" use and should thus *usually* be safe inside Docker
		echo 'RemoteIPTrustedProxy 10.0.0.0/8'; \
		echo 'RemoteIPTrustedProxy along / 12'; \
		echo 'RemoteIPTrustedProxy 192.168.0.0/16'; \
		echo 'RemoteIPTrustedProxy 169.254.0.0/16'; \
		echo 'RemoteIPTrustedProxy 127.0.0.0/8'; \
	} > /etc/apache2/conf-available/remoteip.conf; \
	a2enconf remoteip; \
# https://github.com/docker-library/wordpress/issues/383#issuecomment-507886512
# (replace all instances of "%h" with "%a" in LogFormat)
	find /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+"[^"]*)%h([^"]*")/\1%a\2/g' '{}' +
Copy the code

This code snippet addresses common issues such as how to properly install GD libraries, Imageick, and how to get the correct access IP address in the container environment. If you are interested, you can browse the comment status link in the code snippet for more detailed information.

Application and application dependent installation

The installation of PHP applications in containers is generally divided into two types: using the source code compression package for installation, and using system software packages (such as APT) for installation.

In this article, the more general-purpose source code compression package is used for installation. Considering the maintainability of the image, the version of the software package is parameterized and the code package is fingerprinted to avoid some “security problems” :

ARG DOKUWIKI_VERSION=2020-07-29
ARG DOKUWIKI_SHASUM=119f3875d023d15070068a6aca1e23acd7f9a19a

RUN set -eux; \
	curl -o dokuwiki-stable.tgz -fL "https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz"; \
	echo "$DOKUWIKI_SHASUM  dokuwiki-stable.tgz" | sha1sum -c -; \
	\
	tar -xvf dokuwiki-stable.tgz -C /usr/src/; \
	rm dokuwiki-stable.tgz; \
    mkdir -p /usr/src/dokuwiki; \
    cp -r /usr/src/dokuwiki-$DOKUWIKI_VERSION/* /usr/src/dokuwiki; \
    rm -rf /usr/src/dokuwiki-$DOKUWIKI_VERSION; \
	chown -R www-data:www-data /usr/src/dokuwiki;
Copy the code

One of the obvious benefits of using this pattern to build an image is that when a new version of the application comes out, we can rebuild the image at a very low cost, the base part doesn’t need to be rebuilt at all, the old Docker Layer cache can be reused greatly, and the application updates can be avoided. Need to update the Dockerfile maintenance, modify the content related to the application.

The dependency installation of PHP applications is generally divided into two types, one is “system-level” dependency, as mentioned above, and the other is software package dependency based on Composer. Its core principle is that Composer downloads the code package of application dependency by reading the project dependency configuration file. Then a loader file is dynamically generated for application use.

DokuWiki doesn’t use Composer, so we’ll save that for the next PHP application containerization.

If you are interested in Composer, check out the previous article:

  • Build a high-performance private Composer mirroring service
  • How to use Composer with CI system

Apply configuration Settings

Application configuration Settings include creating and changing the default configuration files used by applications, initializing databases, and configuring Web server rules on which applications depend.

For application configuration file creation and content modification, the recommended method is to use file mount or dynamic distribution after application startup to decouple “user files” from “default application files”.

As mentioned earlier, DokuWiki is easy to install and doesn’t have to rely on a database, so I’ll leave database initialization for other applications.

In general, it is recommended to configure rules for Web servers in the same way as application configuration files. However, if the configuration does not need to be changed and is a “fixed routine”, you can initialize the rules in the following ways:

RUN [ ! -e /usr/src/dokuwiki/.htaccess ]; \ {\echo 'Options -Indexes -MultiViews +FollowSymLinks'; \
        echo ' 
      
       '
      ; \
        echo ' 
      
       '
      ; \
        echo ' Require all denied'; \
        echo ' '; \
        echo ' 
      
       '
      ; \
        echo ' Order allow,deny'; \
        echo ' Deny from all'; \
        echo ' '; \
        echo ' '; \
        echo ' 
      
       '
      ; \
        echo ' RedirectMatch 404 /\.git'; \
        echo ' '; \

        echo 'RewriteEngine on'; \
        echo 'RewriteRule ^_media/(.*) lib/exe/fetch.php? media=$1 [QSA,L]'; \
        echo 'RewriteRule ^_detail/(.*) lib/exe/detail.php? media=$1 [QSA,L]'; \
        echo 'RewriteRule ^_export/([^/]+)/(.*) doku.php? do=export_$1&id=$2 [QSA,L]'; \
        echo 'RewriteRule ^$ doku.php [L]'; \
        echo 'RewriteCond %{REQUEST_FILENAME} ! -f'; \
        echo 'RewriteCond %{REQUEST_FILENAME} ! -d'; \
        echo 'RewriteRule (.*) doku.php? id=$1 [QSA,L]'; \
        echo 'RewriteRule ^index.php$ doku.php'; \
        echo 'RewriteBase /'; \
	} > /usr/src/dokuwiki/.htaccess; \
	\
	chown -R www-data:www-data /usr/src/dokuwiki;
Copy the code

Configuring the Startup Portal

Most of the time, for PHP applications we don’t have to configure the application launch portal, just declare how the application runs in the foreground (thin container category).

CMD ["apache2-foreground"]
Copy the code

But writing a Docker-entrypoint script may be necessary if a partial file mount scenario is involved, or if post-scripts processing logic is performed based on environment variables and additional deltas are performed based on the running network after startup.

COPY docker-entrypoint.sh /usr/local/bin/

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]
Copy the code

To be lazy, I also used a WordPress snippet from the Docker repository and tweaked it for DokuWiki:

#! /usr/bin/env bash
set -Eeuo pipefail

if [[ "The $1" == apache2* ]] || [ "The $1" = 'php-fpm' ]; then
	uid="$(id -u)"
	gid="$(id -g)"
	if [ "$uid" = '0' ]; then
		case "The $1" in
			apache2*)
				user="${APACHE_RUN_USER:-www-data}"
				group="${APACHE_RUN_GROUP:-www-data}"

				# strip off any '#' symbol ('#1000' is valid syntax for Apache)
				pound=The '#'
				user="${user#$pound}"
				group="${group#$pound}"
				;;
			*) # php-fpm
				user='www-data'
				group='www-data'
				;;
		esac
	else
		user="$uid"
		group="$gid"
	fi

	if [ ! -e index.php ] && [ ! -e VERSION ]; then
		# if the directory exists and dokuwiki doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory)
		if [ "$uid" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0-0 draw' ]; then
			chown "$user:$group" .
		fi

		echo2 > &"dokuwiki not found in $PWD - copying now..."
		if [ -n "$(find -mindepth 1 -maxdepth 1 -not -name wp-content)" ]; then
			echo2 > &"WARNING: $PWD is not empty! (copying anyhow)"
		fi
		sourceTarArgs=(
			--create
			--file -
			--directory /usr/src/dokuwiki
			--owner "$user" --group "$group"
		)
		targetTarArgs=(
			--extract
			--file -
		)
		if [ "$uid"! ='0' ]; then
			# avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted"
			targetTarArgs+=( --no-overwrite-dir )
		fi
		# loop over "pluggable" content in the source, and if it already exists in the destination, skip it
		# https://github.com/docker-library/wordpress/issues/506 ("wp-content" persisted, "akismet" updated, dokuwiki container restarted/recreated, "akismet" downgraded)
		for contentPath in \
			/usr/src/dokuwiki/.htaccess \
			/usr/src/dokuwiki/data/*/*/ \
			/usr/src/dokuwiki/conf/* \
			/usr/src/dokuwiki/lib/*/* \
		; do
			contentPath="${contentPath%/}"
			[ -e "$contentPath"] | |continue
			contentPath="${contentPath#/usr/src/dokuwiki/}" # "wp-content/plugins/akismet", etc.
			if [ -e "$PWD/$contentPath" ]; then
				echo2 > &"WARNING: '$PWD/$contentPath' exists! (not copying the dokuwiki version)"
				sourceTarArgs+=( --exclude ". /$contentPath" )
			fi
		done
		tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
		echo2 > &"Complete! dokuwiki has been successfully copied to $PWD"
	fi

fi

if [ -d /var/www/patches/inc/ ]; then
	for contentPath in \
		/var/www/patches/inc/* \
	; do
		cp $contentPath /var/www/html/inc/
		echo "Patch:$contentPath"
	done
fi

exec "$@"
Copy the code

The above startup script does three main things:

  1. Throw the application source code into the directory used by the Web server and set strict permissions.
  2. If some application files are mapped to the local PC, perform some additional data initialization operations to avoid application running exceptions after files are mounted.
  3. Use lightweight file patches to modify and replace application core files other than application plug-ins and templates.

Write container choreography files

Combined with the container configuration above, it is not difficult to write clear declarative choreography files.

version: "2"

services:

  dokuwiki:
    image: soulteary/docker-dokuwiki:2020-07-29
    ports:
      - 8080: 80
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./pages:/var/www/html/data/pages:rw
      - ./meta:/var/www/html/data/meta:rw
      - ./media:/var/www/html/data/media:rw
      - ./conf:/var/www/html/conf:rw
      - ./plugins:/var/www/html/lib/plugins:rw
      - ./docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh:ro
      - ./patches:/var/www/patches:ro
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
Copy the code

Save the above file as docker-compose. Yml and start the application with docker-compose up -d. Visit 8080 and see DokuWiki running in the container.

If you are a Traefik user, you can use the following configuration:

version: "2"

services:

  dokuwiki:
    image: soulteary/docker-dokuwiki:2020-07-29
    networks:
      - traefik
    expose:
      - 80
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./pages:/var/www/html/data/pages:rw
      - ./meta:/var/www/html/data/meta:rw
      - ./media:/var/www/html/data/media:rw
      - ./conf:/var/www/html/conf:rw
      - ./plugins:/var/www/html/lib/plugins:rw
      - ./docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh:ro
      - ./patches:/var/www/patches:ro
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.dokuwiki-web.middlewares=https-redirect@file"
      - "traefik.http.routers.dokuwiki-web.entrypoints=http"
      - "traefik.http.routers.dokuwiki-web.rule=Host(`dokuwiki.wiki`)"
      - "traefik.http.routers.dokuwiki-web.service=dashboard@internal"
      - "traefik.http.routers.dokuwiki-ssl.entrypoints=https"
      - "traefik.http.routers.dokuwiki-ssl.rule=Host(`dokuwiki.wiki`)"
      - "traefik.http.routers.dokuwiki-ssl.tls=true"
      - "traefik.http.services.dokuwiki-backend.loadbalancer.server.scheme=http"
      - "traefik.http.services.dokuwiki-backend.loadbalancer.server.port=80"
    extra_hosts:
      - "Dokuwiki. Wiki: 127.0.0.1."
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

networks:
  traefik:
    external: true
Copy the code

Application initialization

You can see that DokuWiki is up and running, but before you can use it, you need to install and configure the DokuWiki Installer by going to /install.php.

Fill in the relevant information according to your own needs, click “Save”, about 1 to 2 seconds later, the page jumps and prompts “initialization completed”. At this point, you can officially start your DokuWiki tour. More details on DokuWiki usage and optimizations will not be covered in this article, but will be covered in a future article.

other

The relevant code in this article, I have uploaded to GitHub: github.com/soulteary/d… , welcome to submit PR, let the application mirror change more perfect.

The last

The next DokuWiki article will share how to use it better for knowledge management, project management, personal blogging, and even CMS.

–EOF


We have a little group of people who like to do things.

In the case of no advertisement, we will talk about software and hardware, HomeLab and programming problems together, and also share some information of technical salon irregularly in the group.

Like to toss small partners welcome to scan code to add friends. (Please indicate the source and purpose, and note the real name, otherwise it will not pass the audit)

All this stuff about getting into groups


This article is published under a SIGNATURE 4.0 International (CC BY 4.0) license. Signature 4.0 International (CC BY 4.0)

Author: Su Yang

Creation time: on June 23, 2021 statistical word count: 11924 words reading time: 24 minutes to read this article links: soulteary.com/2021/06/23/…