Skip to content

Commit

Permalink
BREAKING CHANGE: Migrate to alpine+nginx+php8
Browse files Browse the repository at this point in the history
  • Loading branch information
marverix committed Jan 9, 2024
1 parent 5dc3db8 commit 9d57470
Show file tree
Hide file tree
Showing 20 changed files with 355 additions and 214 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Docker Publish
name: CI

on:
push:
tags: [ 'cp*.*.*-rev*' ]
tags: [ '*.*.*-r*' ]
pull_request:
branches: [ master ]

Expand Down Expand Up @@ -62,11 +62,11 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=match,pattern=cp(\d+\.\d+),group=1
type=match,pattern=cp\d+\.\d+
type=match,pattern=cp([^-]+),group=1
type=match,pattern=cp[^-]+
type=match,pattern=cp.+
type=match,pattern=(\d+\.\d+),group=1
type=match,pattern=\d+\.\d+
type=match,pattern=([^-]+),group=1
type=match,pattern=[^-]+
type=match,pattern=.+
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

## 1.7.1-rc1

* **BREAKING CHANGES**:

* Migrated to Alpine + Nginx + php-fpm. This allowed to reduce memory usage from ~300MB to ~40MB!
* Changed versioning convention from `cpXXX-revYYY` to `CLASSICPRESS_VERSION-rRELEASE_NUMBER`
* Migrated from `APACHE_RUN_USER_ID` and `APACHE_RUN_GROUP_ID` to shared between host and container group `press(gid=2048)`.
* `wp-config.template.php` now contains `define('WP_AUTO_UPDATE_CORE', false);` which should stop CP from auto-updating (this is added only to new installations, so it may be that you must add it manually to your config)
6 changes: 3 additions & 3 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ The simplest way to build this image locally, it's to use Docker Compose.
To do so, you will need to create 2 files: `CP_DB_PASSWORD.secret` and `DB_ROOT_PASSWORD.secret` (files with `.secret` extension are git-ignored).

```sh
export UID=$(id -u)
export GID=$(id -g)
echo "my_secret_password" > secrets/CP_DB_PASSWORD.secret
echo "turbo_secret_password" > secrets/DB_ROOT_PASSWORD.secret
```

Remamber to have a group `press(gid=2048)` and your user is assigned to it.

Now, you simply run:

```sh
docker compose -f docker-compose.dev.yaml up
```

The container will be accessible via http://localhost:8000
The container will be accessible via http://localhost:8080
49 changes: 21 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ Quote from [www.classicpress.net](https://www.classicpress.net/):

> ClassicPress is a community-led open source content management system and a fork of WordPress that preserves the TinyMCE classic editor as the default option.
## Changelog

[Open changelog](https://github.com/marverix/classicpress-docker/blob/master/CHANGELOG.md)

## Tags

### Convention

Tagging convention is: `cpXXX-revYYY`
Tagging convention is: `CLASIC_PRESS_VERSION-rRELEASE`

`CLASIC_PRESS_VERSION` is ClassicPress version, `RELEASE` is Docker Image release number. Eg. `1.7.1-r1`.

`XXX` is ClassicPress version. `YYY` is Docker Image revision number.
## Basic Information

* The image is based on [`php:7.4-apache-bullseye`](https://hub.docker.com/_/php?tab=tags&name=7.4-apache-bullseye)
* The image is based on Alpine 3.16 and php 8.0 (3.16 is a bit old, but it's last version having php8.0 which is required by ClassicPress 1.x)
* Some code taken from [`TrafeX/docker-php-nginx:2.5.0`](https://github.com/TrafeX/docker-php-nginx) which I highly recommend! Unfortunatelly I coudln't use it (inherit) because Docker has no mechanism to "unexpose" port and remove health check
* Thanks to Alpine + Nginx + php-fpm, the image is using only around ~40MB of RAM
* Has enabled all required and recommended php extensions for WordPress
* Has installed [`apache2-mod-security2`](https://github.com/SpiderLabs/ModSecurity) with [enabled OWASP CSR](https://owasp.org/www-project-modsecurity-core-rule-set/)
* Basic security hardening done
* Support for Docker Secrets via env variables with `_FILE` suffix

Note: Even with basic hardening done, it's highly recommended to not to expose a container directly to the outside world. Consider using a reverse proxy like [traefik](https://doc.traefik.io/traefik/) or [Nginx Proxy Manager](https://nginxproxymanager.com/).
Expand All @@ -28,35 +36,22 @@ https://hub.docker.com/r/marverix/classicpress/tags
Good Docker practice is that one service/server == one docker container, this is why you will still need to run separate container
with a database (MySQL/MariaDB).

### Privilages

Apache server inside is using two environment variables to set privilages:

* `APACHE_RUN_USER_ID`
* `APACHE_RUN_GROUP_ID`

By default Docker runs everything with _root_ privilages. There are many great articles describing the impications of this solution, like:
### Write Permission

* [File Permissions: the painful side of Docker](https://blog.gougousis.net/file-permissions-the-painful-side-of-docker/)
* [Permission problems in bind mount in Docker Volume](https://techflare.blog/permission-problems-in-bind-mount-in-docker-volume/)
* [File permissions on Docker volumes](https://ikriv.com/blog/?p=4698)
This image deals with write access shared between host and the container by group `press` (and user) with ID _2048_. This is why your user running the container must be in this group.

This is why this image is running the Apache server as `apache:apache`.
The trick here is, that Apache's user ID (`APACHE_RUN_USER_ID`) and group ID (`APACHE_RUN_GROUP_ID`) are set on fly, to user ID and group ID of the docker container runner. Only downside of this solution is that
you need to set `UID` and `GID` env variables (for example in `~/.bashrc`) like this:
If you are running Debian/Ubuntu-based run on your host machine:

```sh
export UID=$(id -u)
export GID=$(id -g)
sudo groupadd -g 2048 press
sudo usermod $(whoami) -aG press
```

With this, you can use it in Docker Compose like this:
Read more:

```yaml
environment:
- "APACHE_RUN_USER_ID=${UID}"
- "APACHE_RUN_GROUP_ID=${GID}"
```
* [File Permissions: the painful side of Docker](https://blog.gougousis.net/file-permissions-the-painful-side-of-docker/)
* [Permission problems in bind mount in Docker Volume](https://techflare.blog/permission-problems-in-bind-mount-in-docker-volume/)
* [File permissions on Docker volumes](https://ikriv.com/blog/?p=4698)

### With Docker Compose

Expand Down Expand Up @@ -105,8 +100,6 @@ docker-compose -f docker-compose.example.yaml --env-file=myblog-env-example up
--expose 80:80 \
--name myblog \
--volume myblog_data:/data \
--env APACHE_RUN_USER_ID=$UID \
--env APACHE_RUN_GROUP_ID=$GID \
--env CP_DB_NAME=myblog_db \
--env CP_DB_USER=myblog_user \
--env CP_DB_PASSWORD=my_secret_passowrd \
Expand Down
15 changes: 0 additions & 15 deletions classicpress/000-default.conf

This file was deleted.

140 changes: 64 additions & 76 deletions classicpress/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
FROM php:8.1-apache-bullseye
FROM alpine:3.16

LABEL Maintainer="Marek Sierociński"
LABEL Description="ClassicPress image based on trafex/php-nginx"

# Version of ClassicPress
ARG version=1.5.3
ARG corerules_version=3.3.4
ARG version=1.7.1
# ARG corerules_version=3.3.4
ARG www_dir=/var/www/html

ARG WORKDIR_BUILD=/tmp/build
Expand All @@ -13,45 +16,59 @@ ENV WWW_DIR=${www_dir}
ENV DATA_DIR=/data
ENV WP_CONFIG=${DATA_DIR}/wp-config.php
ENV WP_CONTENT=${DATA_DIR}/wp-content
ENV BACKUP_WP_CONTENT="${WWW_DIR}/../wp-content-backup"
ENV PRESS_USER=press
ENV PRESS_HOME=/home/${PRESS_USER}
ENV BACKUP_WP_CONTENT="${PRESS_HOME}/wp-content-backup"

ENV APACHE_RUN_USER=apache
ENV APACHE_RUN_GROUP=www-data
WORKDIR /var/www/html

ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8
# Update packages index
RUN apk update \
# Install packages
&& apk add --no-cache \
bash \
curl \
nginx \
php8 \
php8-ctype \
php8-curl \
php8-dom \
php8-fpm \
php8-gd \
php8-intl \
php8-mbstring \
php8-mysqli \
php8-opcache \
php8-openssl \
php8-phar \
php8-session \
php8-xml \
php8-xmlreader \
php8-zlib \
supervisor

COPY ./ ${WORKDIR_FILES}/

# Install packages
RUN apt-get update \
&& apt-get install -y \
apt-transport-https lsb-release ca-certificates wget gnupg2 \
&& wget -qO - https://modsecurity.digitalwave.hu/archive.key | apt-key add - \
&& sh -c 'echo "deb http://modsecurity.digitalwave.hu/debian/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/dwmodsec.list' \
&& sh -c 'echo "deb http://modsecurity.digitalwave.hu/debian/ $(lsb_release -sc)-backports main" >> /etc/apt/sources.list.d/dwmodsec.list' \
&& apt-get update \
&& apt-get install -y \
libapache2-mod-security2 libmodsecurity3 \
zlib1g-dev libpng16-16 libpng-dev libzip4 libzip-dev locales \
# Ensure UTF-8
&& sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \
&& locale-gen \
# Change working directory
&& mkdir -p ${WORKDIR_DOWNLOADS} \
&& cd ${WORKDIR_DOWNLOADS} \
# Download ClassicPress
# Prepare users
RUN adduser -D -u 2048 press \
&& addgroup press press \
&& addgroup press nginx

# Configure services
RUN cp ${WORKDIR_FILES}/conf/php.ini /etc/php8/conf.d/custom.ini \
&& cp ${WORKDIR_FILES}/conf/fpm-pool.conf /etc/php8/php-fpm.d/www.conf \
&& cp ${WORKDIR_FILES}/conf/nginx.conf /etc/nginx/nginx.conf \
&& cp ${WORKDIR_FILES}/conf/supervisord.conf /etc/supervisord.conf \
&& rm -rf ${www_dir}/*

# Install ClassicPress
RUN mkdir -p ${WORKDIR_DOWNLOADS} && cd ${WORKDIR_DOWNLOADS} \
&& wget -qO classicpress.tar.gz https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${version}.tar.gz \
# Download corerules
&& wget -qO corerules.tar.gz https://github.com/coreruleset/coreruleset/archive/refs/tags/v${corerules_version}.tar.gz \
# Clean www_dir
&& rm -rf ${www_dir}/* \
# Unpack ClassicPress to www_dir
&& tar -xf classicpress.tar.gz -C ${www_dir} --strip-components=1 \
&& tar -xf classicpress.tar.gz -C ${www_dir} --strip-components=1

# Setup
# Create /data
&& mkdir ${DATA_DIR} \
&& cd ${DATA_DIR} \
RUN mkdir ${DATA_DIR} && cd ${DATA_DIR} \
# Move wp-content to /data
&& mv ${www_dir}/wp-content ${WP_CONTENT} \
&& ln -s ${WP_CONTENT} ${www_dir}/wp-content \
Expand All @@ -60,53 +77,24 @@ RUN apt-get update \
&& touch ${WP_CONFIG} \
&& ln -s ${WP_CONFIG} ${www_dir}/wp-config.php \
# Copy wp-config.template.php
&& cp ${WORKDIR_FILES}/wp-config.template.php ${www_dir}/../wp-config.template.php \
# Copy php.ini
&& cp ${WORKDIR_FILES}/php.ini "${PHP_INI_DIR}/php.ini" \
# Install missing php extensions
&& EXTRA_CFLAGS="-I/usr/src/php" docker-php-ext-install \
exif gd mysqli zip \
# Enable apache mod-rewrite
&& a2enmod rewrite \
# Enable apache mod-headers
&& a2enmod headers \
# Enable mod-security2
&& a2enmod security2 \
&& cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf \
&& sed -Ei "s/Sec([A-Z][a-z]+)Engine .+/Sec\1Engine On/g" /etc/modsecurity/modsecurity.conf \
&& cp ${WORKDIR_FILES}/security2.conf /etc/apache2/mods-available/security2.conf \
# Setup mod-security2
&& cd /usr/share/modsecurity-crs \
&& rm -rf ./* \
&& tar -xf ${WORKDIR_DOWNLOADS}/corerules.tar.gz -C ./ --strip-components=1 \
&& mv crs-setup.conf.example crs-setup.conf \
&& mv ./rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example ./rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf \
# Copy default site conf
&& cp ${WORKDIR_FILES}/000-default.conf /etc/apache2/sites-available/000-default.conf \
# Copy security conf
&& cp ${WORKDIR_FILES}/security.conf /etc/apache2/conf-available/security.conf \
# Copy htaccess
&& cp ${WORKDIR_FILES}/htaccess ${DATA_DIR}/.htaccess \
&& ln -s ${DATA_DIR}/.htaccess ${www_dir}/.htaccess \
&& cp ${WORKDIR_FILES}/wp-config.template.php ${PRESS_HOME}/wp-config.template.php \
# Copy startup script
&& cp ${WORKDIR_FILES}/classicpress.sh /opt/classicpress.sh \
# Create user
&& useradd -rMUG daemon,www-data apache \
&& cp ${WORKDIR_FILES}/classicpress.sh ${PRESS_HOME}/classicpress.sh \
# Privilages
&& cp ${WORKDIR_FILES}/ch/chmod /bin/schmod \
&& cp ${WORKDIR_FILES}/ch/chgrp /bin/schgrp \
&& chmod +s /bin/schmod /bin/schgrp \
&& chmod +x ${PRESS_HOME}/classicpress.sh \
&& chown -R ${PRESS_USER}:${PRESS_USER} /data ${www_dir} \
&& chmod -R 777 /run /var/log \
# Clean
&& apt-get purge -y --auto-remove \
wget zlib1g-dev \
&& apt-get autoclean \
&& rm -r /var/lib/apt/lists/* \
&& rm -rf /tmp/*

# Set back www_dir as workdir
WORKDIR ${www_dir}
USER press

# Expose port 80
EXPOSE 80

# It's important to NOT change user with USER. We want to have root permissions
# in startup script, so we can dynamcally change UID/GID and ownership.
HEALTHCHECK --timeout=10s CMD curl --silent --fail http://127.0.0.1/fpm-ping

# Set startup command
CMD [ "/opt/classicpress.sh" ]
CMD [ "/home/press/classicpress.sh" ]
Binary file added classicpress/ch/chgrp
Binary file not shown.
Binary file added classicpress/ch/chmod
Binary file not shown.
Binary file added classicpress/ch/chown
Binary file not shown.
22 changes: 8 additions & 14 deletions classicpress/classicpress.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bash

schgrp -R press /data

# ClassicPress Startup Script
TMP_WP_CONFIG=/tmp/wp-config.php

Expand Down Expand Up @@ -33,16 +35,15 @@ function store_env() {
sed -i "s/$1/${!1}/g" "${TMP_WP_CONFIG}"
}


# Checking wp-config.php
if [ ! -f "${WP_CONFIG}" ]; then
echo "Notice: File ${WP_CONFIG} not found - touching"
touch "${WP_CONFIG}"
fi

if [ ! -w "${WP_CONFIG}" ]; then
echo "Error: File ${WP_CONFIG} found, but not writable. Have you mounted the /data as a volume?"
exit 1
echo "Notice: File ${WP_CONFIG} found, but not writable. Fixing"
schmod g+w "${WP_CONFIG}"
fi

if [ -s "${WP_CONFIG}" ]; then
Expand Down Expand Up @@ -71,7 +72,7 @@ else
random_env "CP_NONCE_SALT"

echo "Preparing wp-config.php ..."
cp "${WWW_DIR}/../wp-config.template.php" "${TMP_WP_CONFIG}"
cp "${PRESS_HOME}/wp-config.template.php" "${TMP_WP_CONFIG}"
store_env "CP_DB_NAME"
store_env "CP_DB_USER"
store_env "CP_DB_PASSWORD"
Expand Down Expand Up @@ -99,14 +100,7 @@ else
cp -r "${BACKUP_WP_CONTENT}" "${WP_CONTENT}"
fi

schmod -R g+w "${WP_CONTENT}"

# Chanigin ownership
echo "Changing ownership..."
sed -Ei "s/$APACHE_RUN_USER:x:[0-9]+:[0-9]+/$APACHE_RUN_USER:x:$APACHE_RUN_USER_ID:$APACHE_RUN_GROUP_ID/g" /etc/passwd
chown -R ${APACHE_RUN_USER}:${APACHE_RUN_GROUP} ${WWW_DIR} ${DATA_DIR}


# Starting apache
echo "Starting Apache in foreground ..."
# https://github.com/docker-library/php/blob/master/7.4/bullseye/apache/apache2-foreground
apache2-foreground
# Starting
supervisord
Loading

0 comments on commit 9d57470

Please sign in to comment.